1

I have implemented my dashboard that is extending ViewGroup using code below. But the result does not displayed as I expected. Dashboard has the buttons as its children views that are properly displayed but the text inside of buttons has an abnormal alignment.

Would you be so kind to explain me why the button label has the positioning effect on resizing and positioning of its container while the one position is set by XML attribute 'gravity'?

What should be done to centering text inside buttons?

Probably, I need the explanation of methods onMeasure() and onLayout(). As I guess, the first method measures the container(dashboard) size by its children views sizes, and the second method changes the position of these children views inside container and resize its. Isn't it? But why the positions and sizes of children views have the effect in onMeasure()?

Thanks in advance.

MainDashboard.java

public class MainDashboard extends ViewGroup {

    /*Attributes: */
    private static final float DEFAULT_FACTOR = 0.2f;
    private static final float DEFAULT_SPACING = 0.025f;
    private static final int DEFAULT_ROW = 1;
    private static final int DEFAULT_COL = 1;

    private int mMaxChildWidth = 0;
    private int mMaxChildHeight = 0;

    /*Attribute variables: */
    private float mFactor;
    private float mSpacing;
    private int mRow;
    private int mCol;

    private DeviceScreenSize dss;
    private SCApplication app;
    private int ell_size;

    public MainDashboard(Context context) {
        super(context, null);
    }

    public MainDashboard(Context context, AttributeSet attrs) {
        super(context, attrs, 0);
        init(context,attrs);
    }

    public MainDashboard(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    private void init(Context context, AttributeSet attrs){
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MainDashboard);

        mFactor = a.getFloat(R.styleable.MainDashboard_factor, DEFAULT_FACTOR);
        mSpacing = a.getFloat(R.styleable.MainDashboard_spacing, DEFAULT_SPACING);
        mRow = a.getInteger(R.styleable.MainDashboard_row, DEFAULT_ROW);
        mCol = a.getInteger(R.styleable.MainDashboard_col, DEFAULT_COL);

        app = (SCApplication)context.getApplicationContext();
        dss= app.getScreenSize();
        ell_size = (int) (Math.min(dss.getWidth(),dss.getHeight())*mFactor);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        mMaxChildWidth = 0;
        mMaxChildHeight = 0;

        // Measure once to find the maximum child size.
        int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.AT_MOST);
        int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.AT_MOST);

        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() == GONE) {
                continue;
            }
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

            mMaxChildWidth = Math.max(mMaxChildWidth, child.getMeasuredWidth());
            mMaxChildHeight = Math.max(mMaxChildHeight, child.getMeasuredHeight());
        }
        // Measure again for each child to be exactly the same size.
        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                mMaxChildWidth, MeasureSpec.EXACTLY);
        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                mMaxChildHeight, MeasureSpec.EXACTLY);

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() == GONE) {
                continue;
            }
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }

        setMeasuredDimension(
                resolveSize(mMaxChildWidth, widthMeasureSpec),
                resolveSize(mMaxChildHeight, heightMeasureSpec));
    }


    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int container_width = r - l;
        int container_height = b - t;
        final int count = getChildCount();

        int visibleCount = 0;
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() == GONE) {
                continue;
            }
            ++visibleCount;
        }

        if (visibleCount == 0) {
            return;
        }

        int left, top;
        int hSpace=(int)(mSpacing *container_width),
            vSpace=(int)(2 * mSpacing * container_width);
        int col, row;
        int visibleIndex = 0;
        for (int i = 0; i < visibleCount; i++) {
            final View child = getChildAt(i);
            row = visibleIndex / mCol;
            col = visibleIndex % mCol;
            left = hSpace * (col + 1) + ell_size * col;
            top = vSpace * (row + 1) + ell_size * row;
            child.layout(left, top, left + ell_size, top + ell_size);
            ++visibleIndex;
        }
    }
}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:whatever="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/llRoot"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    android:background = "#000000" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="7"
        android:gravity="center">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/tvMainTitle"
            style="@android:style/TextAppearance.Large"
            android:text="@string/main_title"
            android:textColor="#4169E1"
            />
    </LinearLayout>

    <com.sample.myapp.MainDashboard
        xmlns:android = "http://schemas.android.com/apk/res/android"
        android:id="@+id/dash"
        android:layout_width = "match_parent"
        android:layout_height = "match_parent"
        android:layout_weight = "1"
        android:background = "#000000"
        whatever:col="4">

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/DashboardButton"
                android:id="@+id/btn1"
                android:text="1"
                />
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/DashboardButton"
                android:id="@+id/btn2"
                android:text="2"
                />
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/DashboardButton"
                android:id="@+id/btn3"
                android:text="3"
                />
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/DashboardButton"
                android:id="@+id/btn4"
                android:text="4"
                />
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/DashboardButton"
                android:id="@+id/btn5"
                android:text="5"
                />
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/DashboardButton"
                android:id="@+id/btn6"
                android:text="6"
                />
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/DashboardButton"
                android:id="@+id/btn7"
                android:text="7"
                />
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/DashboardButton"
                android:id="@+id/btn8"
                android:text="8"
                />
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/DashboardButton"
                android:id="@+id/btn9"
                android:text="9"
                />
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/DashboardButton"
                android:id="@+id/btn10"
                android:text="10"
                />
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/DashboardButton"
                android:id="@+id/btn11"
                android:text="11"
                />
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/DashboardButton"
                android:id="@+id/btn12"
                android:text="12"
                />
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/DashboardButton"
                android:id="@+id/btn13"
                android:text="13"
                />
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/DashboardButton"
                android:id="@+id/btn14"
                android:text="14"
                />
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/DashboardButton"
                android:id="@+id/btn15"
                android:text="15"
                />
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/DashboardButton"
                android:id="@+id/btn16"
                android:text="16"
                />
    </com.sample.myapp.MainDashboard>
</LinearLayout>

styles.xml

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <style name = "DashboardButton">
        <item name = "android:textSize">14sp</item >
        <item name = "android:textStyle">bold</item >
        <item name = "android:textColor">#FFFFFF</item>
        <item name = "android:gravity">center_horizontal</item >
        <item name = "android:layout_gravity">center_vertical</item >
        <item name = "android:background">@drawable/defaultbuttonselector</item >
        <item name = "android:layout_width">wrap_content</item >
        <item name = "android:layout_height">wrap_content</item >
    </style>

</resources>

defaultbuttonselector.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item>
        <shape
            android:shape="rectangle" >

            <corners android:radius="10.0dp" />

            <stroke
                android:width="2dp"
                android:color="#4169E1" />   
        </shape>
    </item>

    <item android:state_pressed="true">
        <shape android:shape="rectangle">
            <corners android:radius="10.0dip" />
            <stroke
                android:width="3dp"
                android:color="#00f2ff" />
        </shape>
    </item>

</selector>

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="MainDashboard">
        <attr name="factor" format="float" />
        <attr name="spacing" format="float" />
        <attr name="row" format="integer" />
        <attr name="col" format="integer" />
    </declare-styleable>

</resources>

MainActivity.java

public class MainActivity extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

left - that I have; right - that I need

Sergey V.
  • 981
  • 2
  • 12
  • 24

1 Answers1

0

After some educated guesses and tearing my hairs I found solution of this issue.

The point is that in my case (with encountered size ell_size for every child item of the custom dashboard) in the body of onMeasure() I just use ell_size to set childWidthMeasureSpec and childHeightMeasureSpec instead of using widthMeasureSpec and heightMeasureSpec. I don't know whether it is a greate idea but that helped me and I got that I need.

The snippet of the code above:

private void init(Context context, AttributeSet attrs){
    ...
    ell_size = (int) (Math.min(dss.getWidth(),dss.getHeight())*mFactor);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    // Measure once to find the maximum child size.
    int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
            ell_size, MeasureSpec.AT_MOST);
    int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
            ell_size, MeasureSpec.AT_MOST);

    final int count = getChildCount();
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() == GONE) {
            continue;
        }
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

    // Measure again for each child to be exactly the same size.
    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
            ell_size, MeasureSpec.EXACTLY);
    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
            ell_size, MeasureSpec.EXACTLY);

    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() == GONE) {
            continue;
        }
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

    setMeasuredDimension(
            resolveSize(ell_size, widthMeasureSpec),
            resolveSize(ell_size, heightMeasureSpec));
}

I hit upon this idea from the discription of function layout():

Assign a size and position to a view and all of its descendants. This is the second phase of the layout mechanism. (The first is measuring). In this phase, each parent calls layout on all of its children to position them. This is typically done using the child measurements that were stored in the measure pass(). Derived classes should not override this method. Derived classes with children should override onLayout. In that method, they should call layout on each of their children.

Sergey V.
  • 981
  • 2
  • 12
  • 24