Saturday, May 19, 2012

Android: Expand Liner Layout Beyond its Width

In android, the layout weight parameter in the liner layout is very useful to layout views with relative ratios. Say you want to arrange two frames left and right, and right frame take quoter and right take the balance.


We can simply create this using following layout xml. Only thing is to noted down is, the width should be set to 0dip and weight to be set to 0.25 or 0.75 according to the requirement. That all.


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal"
    android:weightSum="1" >

    <FrameLayout
        android:layout_width="0dip"
        android:layout_height="match_parent"
        android:layout_weight="0.75"
        android:background="#FF0000" >
    </FrameLayout>

    <FrameLayout
        android:layout_width="fill_parent"
        android:layout_height="match_parent"
        android:layout_weight="0.25"
        android:background="#00FF00" >

        <ToggleButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center" />
    </FrameLayout>

</LinearLayout>


But in our case, we wanted to add another frame next to the right frame its width also 0.75 of the screen and in some user action, by moving the screen, the left frame should be hidden and 3rd frame should be shown.



So we created custom liner layout by extending the liner layout, where we override the on measure method to adjust its size by 0.75 of its size.


public class ExpandedLinearLayout extends LinearLayout {
@SuppressWarnings("unused")
private static final String TAG = "demo.beyond.layout.ExpandedLinearLayout";
public ExpandedLinearLayout(Context context) {
super(context);
}
public ExpandedLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ExpandedLinearLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension((int) (1.75f*getMeasuredWidth()), getMeasuredHeight());
}
}



And add another new frame to above layout with weight 0.75.


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
.................    
    <FrameLayout
        android:layout_width="0dip"
        android:layout_height="match_parent"
        android:layout_weight="0.75"
        android:background="#0000FF" >
    </FrameLayout>

</LinearLayout>


Then we add the animation for the on click.


public class DemoExpandLayoutActivity extends Activity implements OnClickListener {
        private LinearLayout mLinearLayout;
private FrameLayout mFrameLayout1;
private ToggleButton mToggleButton;
/** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mLinearLayout = (LinearLayout)findViewById(R.id.frame);
        mFrameLayout1 = (FrameLayout)findViewById(R.id.frameLayout1);
        mToggleButton = (ToggleButton)findViewById(R.id.toggleButton1);
        mToggleButton.setOnClickListener(this);
    }

@Override
public void onClick(View arg0) {
float factor = 1.0f*mFrameLayout1.getWidth()/mLinearLayout.getWidth();
if(mToggleButton.isChecked()){
mLinearLayout.setTranslationX(-mFrameLayout1.getWidth());
TranslateAnimation translateAnimation = new TranslateAnimation(
TranslateAnimation.RELATIVE_TO_PARENT,
factor,
TranslateAnimation.RELATIVE_TO_PARENT,
0.0f,
TranslateAnimation.RELATIVE_TO_PARENT,
0.0f,
TranslateAnimation.RELATIVE_TO_PARENT,
0.0f);
translateAnimation.setDuration(1000);
LayoutAnimationController layoutAnimationController = new LayoutAnimationController(translateAnimation,0.1f);
mLinearLayout.setLayoutAnimation(layoutAnimationController);
}else{
mLinearLayout.setTranslationX(0);
TranslateAnimation translateAnimation = new TranslateAnimation(
TranslateAnimation.RELATIVE_TO_PARENT,
-factor,
TranslateAnimation.RELATIVE_TO_PARENT,
0.0f,
TranslateAnimation.RELATIVE_TO_PARENT,
0.0f,
TranslateAnimation.RELATIVE_TO_PARENT,
0.0f);
translateAnimation.setDuration(1000);
LayoutAnimationController layoutAnimationController = new LayoutAnimationController(translateAnimation,-0.1f);
mLinearLayout.setLayoutAnimation(layoutAnimationController);
}
}
}


But that didn't quit worked out, because liner layout ignores views If those out side the weight sum.

But if we add views as match patent, with layout weights, liner layout behaves differently. It draws all the views beyond its boundary.
In this case the actual width of each view was not proportional to layout weight what we set in layout XML.
So to find out the relation, I just try for different values and plot the following graph and got the below equation.



I couldn't find any direct evidence in android code base or may have to check some more android source code. But this is good enough to calculate the proper values for us.

Then I changed the layout XML with those values.


<?xml version="1.0" encoding="utf-8"?>
<demo.beyond.layout.ExpandedLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/frame"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal"
    android:weightSum="1" >

    <FrameLayout
        android:id="@+id/frameLayout1"
        android:layout_width="fill_parent"
        android:layout_height="match_parent"
        android:layout_weight="0.125"
        android:background="#FF0000" >
    </FrameLayout>

    <FrameLayout
        android:id="@+id/frameLayout2"
        android:layout_width="fill_parent"
        android:layout_height="match_parent"
        android:layout_weight="0.375"
        android:background="#00FF00" >

        <ToggleButton
            android:id="@+id/toggleButton1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center" />
    </FrameLayout>

    <FrameLayout
        android:id="@+id/frameLayout3"
        android:layout_width="fill_parent"
        android:layout_height="match_parent"
        android:layout_weight="0.125"
        android:background="#0000FF" >
    </FrameLayout>

</demo.beyond.layout.ExpandedLinearLayout>


Further more I modified the expanded liner layout with this formula, so that it can use in all cases.


@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int numberOfChild = getChildCount();
float totalWidth = 0;
for(int i = 0;i < numberOfChild;i++){
totalWidth+=getMeasuredWidth()
*(1 - 2*((LinearLayout.LayoutParams)getChildAt(i).getLayoutParams()).weight);
}
setMeasuredDimension((int) totalWidth, getMeasuredHeight());
}


Here I have uploaded the source code, try it your self and let me know your feedback or let me know if you have any other option.





Tuesday, May 15, 2012

Android: How to Check if Media Scanner is Running?

In our application, some of the action need to be blocked while android media scanner running (For example loading existing media and listing down).

In this case, we couldn't find a direct API to check if Media Scanner is running. We could find following two options:
Option 01:
Android will broadcast Intent.ACTION_MEDIA_SCANNER_STARTEDIntent.ACTION_MEDIA_SCANNER_FINISHED in starting and finishing of scanning respectively. So we create following  BroadcastReceiver:

public class MediaScannerBroadcastReceiver extends BroadcastReceiver {
public static boolean mMedaiScanning = false;
        @Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals(Intent.ACTION_MEDIA_SCANNER_STARTED)){
mMedaiScanning = true;
}
if(intent.getAction().equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)){
mMedaiScanning = false;
}
}
}


And register this in manifest file:

<receiver android:name=" MediaScannerBroadcastReceiver">
     < intent-filter>

           < action android:name="android.intent.action.MEDIA_SCANNER_FINISHED" >  < /action>
          < action android:name="android.intent.action.MEDIA_SCANNER_STARTED" >  < /action>
           < data android:scheme="file" >  < /data>
      < /intent-filter>
</receiver>


Then in cases we we wanted to check the condition we sued as following:
if( MediaScannerBroadcastReceiver.mMedaiScanning){
     .....
}

But, most of the devices will run Media Scanner in boot up, in adding SDCard or mount/unmount devices to PC. In those cases our application is invoked and loaded into memory.

Option 02:
found that the media scanner service insert and delete a record into media store when starting and finishing the scanning. So we try to check the record in the media store as follows:

private boolean isMediaScannerRunning() {
Cursor query = getContentResolver().query(MediaStore.getMediaScannerUri(),
                                                   new String[]{MediaStore.MEDIA_SCANNER_VOLUME}, nullnullnull);
        if(query!=null){
         if(query.moveToFirst()){
         int columnIndex = query.getColumnIndex(MediaStore.MEDIA_SCANNER_VOLUME);
         String volumeName = query.getString(columnIndex);
         if(volumeName!=null){
         return true;
         }
         }
         query.close();
        }
        return false;
}

This option will not load the application while media scanner running. But need to consider its costly operation in query.

Lets check both options for its pros and cons.