首頁  >  文章  >  Java  >  Android中使用自訂ViewGroup的總結

Android中使用自訂ViewGroup的總結

高洛峰
高洛峰原創
2017-01-16 16:24:211492瀏覽

分類

自訂Layout可以分為兩種情況。

自訂ViewGroup,創造出一些不同於LinearLayout,RelativeLayout等之類的ViewGroup。例如:API 14以後增加的GridLayout、design support library中的CoordinatorLayout等等。

自訂一些已經有的Layout然後加入一些特殊的功能。例如:TableLayout以及percent support library中的PercentFrameLayout等等。

流程

自訂View的流程是:onMeasure()->onLayout()->onDraw()。自訂ViewGroup的時候通常是不要去實作onDraw的,當然也可能有特別的需求,例如:CoordinatorLayout。

所以onMeasure和onLayout基本上能做大部分我們接觸的ViewGroup。但是僅僅的知道在onMeasure中測量ViewGroup的大小以及在onLayout中計算Child View的位置還是不夠。

例如:怎麼可以為ViewGroup的Child View設定屬性?

一個例子。 

寫一個自訂的ViewGroup,增加一個屬性控制Child View的大小(長寬)佔ViewGroup的比例。

假設是一個LinearLayout,那就先定義一個CustomLinearLayout。

public class CustomLinearLayout extends LinearLayout {
  public CustomLinearLayout(Context context) {
    super(context);
  }
 
  public CustomLinearLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
  }
 
  public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
  }
 
  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
  }
}

其它拋開不說,我們是需要增加屬性用來控制Child View的大小。那就先在value/attr.xml中定義那個屬性(使用CustomLinearLayout_Layout來與CustomLinearLayout區分下,當然這個名字是隨意的)。

<declare-styleable name="CustomLinearLayout_Layout">
  <!-- 定义比例 -->
  <attr name="inner_percent" format="float"/>
</declare-styleable>

ViewGroup呼叫addView()的時候最後都會呼叫到這個方法。

public void addView(View child, int index, LayoutParams params)

這個params代表的就是View的配置,ViewGroup.LayoutParams中就包含了width、height,LinearLayout.LayoutParams增加了weight屬性等等。那我們就應該實作一個LayoutParams。那麼現在就是這樣了。

public class CustomLinearLayout extends LinearLayout {
  public CustomLinearLayout(Context context) {
    super(context);
  }
 
  public CustomLinearLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
  }
 
  public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
  }
 
  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
  }
  public static class LayoutParams extends LinearLayout.LayoutParams {
 
    private float innerPercent;
 
    private static final int DEFAULT_WIDTH = WRAP_CONTENT;
    private static final int DEFAULT_HEIGHT = WRAP_CONTENT;
 
    public LayoutParams() {
      super(DEFAULT_WIDTH, DEFAULT_HEIGHT);
      innerPercent = -1.0f;
    }
 
    public LayoutParams(float innerPercent) {
      super(DEFAULT_WIDTH, DEFAULT_HEIGHT);
      this.innerPercent = innerPercent;
    }
 
    public LayoutParams(ViewGroup.LayoutParams p) {
      super(p);
    }
 
    @TargetApi(Build.VERSION_CODES.KITKAT)
    public LayoutParams(LinearLayout.LayoutParams source) {
      super(source);
    }
 
    @TargetApi(Build.VERSION_CODES.KITKAT)
    public LayoutParams(LayoutParams source) {
      super(source);
      this.innerPercent = source.innerPercent;
    }
 
    public LayoutParams(Context c, AttributeSet attrs) {
      super(c, attrs);
      init(c, attrs);
    }
 
    private void init(Context context, AttributeSet attrs) {
      TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomLinearLayout_Layout);
      innerPercent = a.getFloat(R.styleable.CustomLinearLayout_Layout_inner_percent, -1.0f);
      a.recycle();
    }
  }
}

   

現在就可以在xml中使用我們的屬性了。

<?xml version="1.0" encoding="utf-8"?>
<com.egos.samples.custom_layout.CustomLinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:layout_width="200dp"
  android:layout_height="200dp"
  android:id="@+id/test_layout"
  android:background="#ffff0000"
  android:gravity="center"
  android:orientation="vertical">
  <ImageView
    android:text="Egos"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:onClick="add"
    android:background="#ff00ff00"
    app:inner_percent="0.8"/>
</com.egos.samples.custom_layout.CustomLinearLayout>

   

只是然並軟,並沒有作用。

那麼到底是怎麼去控制Child View的大小呢?當然是在onMeasure控制的。 addView會執行下面的程式碼。

requestLayout();
invalidate(true);

   

這樣的話就會重新的走一遍onMeasure(),onLayout()了。實作onMeasure()的方法以後直接去處理Child View的大小,因為我繼承的是LinearLayout,所以其實是會處理到measureChildBeforeLayout()。最終是在measureChildBeforeLayout的時候來處理Child View的大小。

@Override
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec,
                    int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
  // 在xml强制写成match_parent,然后在这里强制设置成
  if (child != null && child.getLayoutParams() instanceof LayoutParams &&
      ((LayoutParams) child.getLayoutParams()).innerPercent != -1.0f) {
    parentWidthMeasureSpec = MeasureSpec.makeMeasureSpec((int) (MeasureSpec.getSize(parentWidthMeasureSpec) *
        ((LayoutParams) child.getLayoutParams()).innerPercent), MeasureSpec.getMode(parentWidthMeasureSpec));
    parentHeightMeasureSpec = MeasureSpec.makeMeasureSpec((int) (MeasureSpec.getSize(parentHeightMeasureSpec) *
        ((LayoutParams) child.getLayoutParams()).innerPercent), MeasureSpec.getMode(parentHeightMeasureSpec));
    super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed,
      parentHeightMeasureSpec, heightUsed);
  } else {
    super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed,
        parentHeightMeasureSpec, heightUsed);
  }
}

這樣就可以實現最開始的需求了。

Android中使用自訂ViewGroup的總結

其實還有一些細節是需要處理的,下面的程式碼就是。

/**
 * 当checkLayoutParams返回false的时候就会执行到这里的generateLayoutParams
 */
 @Override
protected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
  return super.generateLayoutParams(lp);
}
 
/**
 * 当addView的时候没有设置LayoutParams的话就会默认执行这里的generateDefaultLayoutParams
 */
@Override
protected LayoutParams generateDefaultLayoutParams() {
  return new LayoutParams();
}
 
/**
 * 写在xml中属性的时候就会执行这里的generateLayoutParams
 */
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
  return new LayoutParams(getContext(), attrs);
}

總結一下

自訂View和ViewGroup需要多注意的都是onMeasure、onLayout、onDraw。

把ViewGroup本身的屬性和Child View的屬性區分開。

可以多多的參考support套件中的程式碼,調試也非常的方便。

做Android開發,自身需要自訂View的地方確實是比較的多,只是大部分都會有對應的開源函式庫。但是我們還是應該需要熟練的知道該如何自訂一個ViewGroup。

更多Android使用自訂ViewGroup的總結相關文章請關注PHP中文網!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn