官术网_书友最值得收藏!

Measuring our custom view

In the quick example we built in the previous chapter, we delegated all sizes and measurement to the parent view itself. To be honest, we haven't even delegated it; we just didn't do anything specifically to take care of that. Being able to control the size and dimensions of our custom view is something we definitely need to pay some attention to. To start, we're going to override the onMeasure() method from view as follows:

@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
    super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
} 

Reading the Android documentation about the onMeasure() method, we should see we must call either setMeasuredDimension(int, int) or the superclass' onMeasure(int, int). If we forget to do so, we'll get an IllegalStateException:

com.packt.rrafols.customview E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.packt.rrafols.customview, PID: 13601
java.lang.IllegalStateException: View with id -1: com.packt.rrafols.customview.OwnCustomView#onMeasure() did not set the measured dimension by calling setMeasuredDimension() at android.view.View.measure(View.java:18871)

There are three different modes in which our view's parent can indicate to our view how it should calculate its size. We can get the mode by using the MeasureSpec.getMode(int) method with each size spec widthMeasureSpec and heightMeasureSpec.

These modes are as follows:

  • MeasureSpec.EXACTLY
  • MeasureSpec.AT_MOST
  • MeasureSpec.UNSPECIFIED

We'll get MeasureSpec.EXACTLY when the size has been calculated or decided by the parent. Our view will have that size even if it requires or returns a different size. If we get MeasureSpec.AT_MOST we have more flexibility: we can be as big as we need but up to the size we also have. Finally, if we received MeasureSpec.UNSPECIFIED, we can size our view to whatever size we want or the view needs.

Using MeasureSpec.getSize(int), we can also get a size value from the size spec.

Now that we have all this, how do we know which values map to the width and height parameters on our XML layout file? Easy to see, let's check. For example, if we specify precise values as shown in the activity_main.xml file in the GitHub repository, we will get the following code:

<com.packt.rrafols.customview.OwnCustomView 
   android:layout_width="150dp" 
   android:layout_height="150dp"/> 

Code on our custom view, using MeasureSpec.toString(int) to get a string description of the measure specification and the size looks like this:

@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
    Log.d(TAG, "width spec: " +
MeasureSpec.toString(widthMeasureSpec)); Log.d(TAG, "height spec: " +
MeasureSpec.toString(heightMeasureSpec)); super.onMeasure(widthMeasureSpec, heightMeasureSpec); }

The result on the Android log is as follows:

D/com.packt.rrafols.customview.OwnCustomView: width : MeasureSpec: EXACTLY 394
D/com.packt.rrafols.customview.OwnCustomView: height: MeasureSpec: EXACTLY 394

Our view will be 394 by 394 pixels exactly. The 394 pixels comes from transforming the 150dp to pixels on the mobile device I was using for testing.

As there are many Android devices with different resolutions and screen densities, we should always use density-independent pixel ( dp) or ( dip) instead of pixels.

For more details about dp, refer to a video by Google available on YouTube: DesignBytes: Density-independent Pixels.

If you would like to convert from dp to real pixels on a specific device, you can use the following method:

public final int dpToPixels(int dp) { 
    return (int) (dp * getResources().getDisplayMetrics().density +
0.5); }

We can see how the conversion is done using the density of the screen, so on different devices the conversion can be different. The + 0.5 in the previous code is just to round up the value when converting from a floating point number to an int.

To convert from pixels to density-independent points, we have to do the inverse operation, as shown in the following code:

public final int pixelsToDp(int dp) { 
    return (int) (dp / getResources().getDisplayMetrics().density +
0.5); }

Let's now see what we receive if we use different measure parameters, such as match_parent or wrap_content, as shown in the activity_main.xml file in the GitHub repository:

<com.packt.rrafols.customview.OwnCustomView 
   android:layout_width="match_parent" 
   android:layout_height="match_parent"/> 

Running the same code as before, we get the following on the Android log:

D/com.packt.rrafols.customview.OwnCustomView: width : MeasureSpec: EXACTLY 996
D/com.packt.rrafols.customview.OwnCustomView: height: MeasureSpec: EXACTLY 1500

So we are still getting a MeasureSpec.EXACTLY, but this time with the size of the parent RelativeLayout; let's try changing one of the match_parents for a wrap_content in activity_main.xml:

<com.packt.rrafols.customview.OwnCustomView 
    android:layout_width="match_parent" 
    android:layout_height="wrap_content"/> 

The result is as follows:

D/com.packt.rrafols.customview.OwnCustomView: width : MeasureSpec: EXACTLY 996
D/com.packt.rrafols.customview.OwnCustomView: height: MeasureSpec: AT_MOST 1500

We can spot an easy-to-follow pattern with MeasureSpec.EXACTLY and MeasureSpec.AT_MOST, but what about MeasureSpec.UNSPECIFIED?

We'll get a MeasureSpec.UNSPECIFIED if our parent is not bounded; for example, if we have a vertical LinearLayout inside a ScrollView, as shown in the scrollview_layout.xml file in the GitHub repository:

<?xml version="1.0" encoding="utf-8"?> 
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" 
    android:orientation="vertical" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent"> 
 
    <LinearLayout 
        android:layout_width="match_parent" 
        android:layout_height="wrap_content" 
        android:orientation="vertical" 
        android:padding="@dimen/activity_vertical_margin"> 
        <com.packt.rrafols.customview.OwnCustomView
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout> </ScrollView>

Then we'll get the following on the Android log:

D/com.packt.rrafols.customview.OwnCustomView: width : MeasureSpec: EXACTLY 996
D/com.packt.rrafols.customview.OwnCustomView: height: MeasureSpec: UNSPECIFIED 1500

That seems alright, but what happens if we now run this code? We'll get an empty screen; our red background we've previously implemented is gone:

That is because we're not managing the size of our custom view. Let's fix that, as shown in the following code:

private static int getMeasurementSize(int measureSpec, int defaultSize) { 
        int mode = MeasureSpec.getMode(measureSpec); 
        int size = MeasureSpec.getSize(measureSpec); 
        switch(mode) { 
            case MeasureSpec.EXACTLY: 
                return size; 
 
            case MeasureSpec.AT_MOST: 
                return Math.min(defaultSize, size); 
 
            case MeasureSpec.UNSPECIFIED: 
            default: 
                return defaultSize; 
        } 
    } 
 
 
    @Override 
    protected void onMeasure(int widthMeasureSpec, int
heightMeasureSpec) { int width = getMeasurementSize(widthMeasureSpec, DEFAULT_SIZE); int height = getMeasurementSize(heightMeasureSpec,
DEFAULT_SIZE); setMeasuredDimension(width, height); }

Now, depending on the measurement specs, we'll set the size of our view by calling the setMeasuredDimension(int, int) method.

For the full example, check the source code in the Example03-Measurement folder in the GitHub repository.

主站蜘蛛池模板: 肥城市| 磐安县| 富顺县| 乐东| 嘉峪关市| 吉木萨尔县| 盘山县| 类乌齐县| 浦县| 彭阳县| 方山县| 大方县| 香格里拉县| 刚察县| 柘城县| 大港区| 玉树县| 布尔津县| 罗江县| 金山区| 托里县| 开封县| 顺平县| 阳新县| 砀山县| 略阳县| 瓮安县| 陆丰市| 平遥县| 永定县| 古浪县| 青州市| 错那县| 梅河口市| 衡山县| 贡嘎县| 若羌县| 石台县| 濮阳市| 绥江县| 波密县|