1. 程式人生 > >View的學習筆記(二) 實現複合控制元件模板

View的學習筆記(二) 實現複合控制元件模板

實現複合控制元件模板


利用已有的View控制元件,來組合創造出自己所需要的複合控制元件,然後提煉成模板,供自己在同一個專案中複用,確保風格統一

定義屬性

讓新的複合控制元件像原生控制元件一樣,可以在xml中設定屬性
在res資源目錄的values目錄下建立attrs.xml屬性定義檔案,格式類似於下面的模板

<resource>
<declare-styleable name="Topbar">
	<attr name="titleText" format="string"/>
	<attr name="titleTextSize" format="dimension"/>
	<attr name="titleTextColor" format="reference|color"/>
</declare-styleable>
</resource>

在程式碼中通過標籤來宣告使用了自定義屬性,並通過name屬性來指定了控制元件名稱
通過標籤來宣告具體的屬性,比如title,titleText,titleTextSize,titleTextColor等屬性name和已經對應的屬性型別format(string/color/dimension/reference/int等)

獲取屬性

在控制元件的構造器中,我們可以通過下面的方法來獲取xml中的配置

 TypedArray ta=context.obtainStyledAttributes(attrs,R.styleable.TopBar);

attrs是構造器中提供的屬性set,R.styleable.TopBar就是我們自己在xml中定義的控制元件name

通過TypedArray來獲取屬性值

ta獲得後,我們就可以通過在xml中定義的屬性name來獲取屬性值

TypedArray ta=context.obtainStyledAttributes(attrs,R.styleable.
TopBar); String mTitleText = ta.getString(R.styleable.NewViewGroup_titleText); float mTitleTextSize = ta.getDimension(R.styleable.NewViewGroup_titleTextSize, 10); int mTitleTextColor = ta.getColor(R.styleable.NewViewGroup_titleTextColor, 0); ta.recycle();

注意最後,要呼叫ta.recycle()及時釋放資源

組合控制元件

獲取引數後就可以組合控制元件了
注意,初始化在構造器(Context context, AttributeSet attrs)中寫,才生效
new出元件控制元件,設定獲得的屬性,然後通過addView新增

//新建控制元件
TextView mTitleView = new TextView(context);
//設定屬性
mTitleView.setText(mTitleText);
mTitleView.setTextSize(mTitleTextSize);
mTitleView.setTextColor(mTitleTextColor);
//設定margin屬性
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
layoutParams.setMargins(40,40,10,10);
//新增view給ViewGroup
addView(mTitleView,layoutParams);

測量子View

如果沒有重寫子View方法,子View是沒有測量值的
這裡假設所有子View是直接上下無縫累加,寬度預設match_parent

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int mHeight=0;
        int count =getChildCount();
        for (int i=0;i<count;i++) {
            View mView = getChildAt(i);
            if (mView.getVisibility() != GONE) {
            //測量子View
                measureChild(mView,widthMeasureSpec,heightMeasureSpec);
                //累加子View的高度和margin
                LinearLayout.LayoutParams lp=(LinearLayout.LayoutParams)mView.getLayoutParams();
                mHeight+=mView.getMeasuredHeight()+lp.topMargin+lp.bottomMargin;
                
            }
        }
         int width=MeasureSpec.getSize(widthMeasureSpec);
         //其實對ViewGroup沒有什麼意義
        setMeasuredDimension(width,mHeight);
    }

如果我們像上面這樣做,group的大小就是內容所有控制元件的大小
如果在xml中新增,指定,顯示的就是指定的大小
在程式碼中新增,顯示的就是測量的大小,而不是通過param指定的大小
這裡無法處理,是因為我們無法獲取viewGroup的width和height屬性
因此開始設法獲取ViewGroup的寬度高度屬性
因此我們需要對viewGroup的LinearLayout.LayoutParams構造器中預設的LayoutParam是LinearLayout)處理(待議)

首先返回自定義屬性部分,增加自定義屬性

<attr name="android:layout_width"/>
<attr name="android:layout_height"/>

然後返回構造器部分,增加程式碼

 		TypedArray ta=context.obtainStyledAttributes(attrs,R.styleable.NewRecyclerView);
        int width=ta.getInt(R.styleable.NewRecyclerView_android_layout_width,0);
        int height=ta.getInt(R.styleable.NewRecyclerView_android_layout_height,0);
        ta.recycle();

放置子View

 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int count =getChildCount();
        int height=0;
        for (int i=0;i<count;i++){
            View mView=getChildAt(i);
            if (mView.getVisibility()!=GONE){
                int mWidth=mView.getMeasuredWidth();
                int mHeight=mView.getMeasuredHeight();
                LinearLayout.LayoutParams lp=(LinearLayout.LayoutParams)mView.getLayoutParams();
				mView.layout(height+lp.topMargin,lp.leftMargin,mWidth+lp.leftMargin+lp.rightMargin,mHeight+lp.topMargin+lp.bottomMargin);
            height+=lp.topMargin+lp.bottomMargin+mHeight;
            }
        }
    }

重寫滑動事件

ViewGroup類都有三個方法

dispatchTouchEvent(MotionEvent ev)
onInterceptTouchEvent(MotionEvent ev)
onTouchEvent(MotionEvent event)

view有兩個事件方法

dispatchTouchEvent(MotionEvent ev)
onTouchEvent(MotionEvent event)

如果

那麼一個觸碰事件,的觸發順序是

ViewGroupA dispatchTouchEvent(MotionEvent ev)
ViewGroupA onInterceptTouchEvent(MotionEvent ev)
ViewGroupB dispatchTouchEvent(MotionEvent ev)
ViewGroupB onInterceptTouchEvent(MotionEvent ev)
ViewC dispatchTouchEvent(MotionEvent ev)
ViewC onTouchEvent(MotionEvent event)
ViewGroupB onTouchEvent(MotionEvent event)
ViewGroupA onTouchEvent(MotionEvent event)

觸控事件的處理,首先判斷需要攔截的情況,在onInterceptTouchEvent(MotionEvent ev)中對應情況下返回true
然後在onTounch()中處理具體的滑動和手指擡起事件

通過介面來增加控制元件的互動

如果我們希望複合控制元件中的某個子控制元件可以點選,或者實現其他事件,可以通過介面來注入,來通過不同的活動裡,同樣的事件實現不同的效果

//介面定義
public interface NewClickListener{
        void titleClick();
}
//介面的回撥
 mTitleView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                mListener.titleClick();
            }
        });
 }
 //介面的注入
  public void setListener(NewClickListener listener){
        this.mListener=listener;
    }

增加模板的拓展性

通過給複合控制元件新增公用方法,來拓展控制元件的多樣性
,或者通過get方法,直接暴露子控制元件給外部呼叫修改

引用UI模板

在引用自定義屬性時,需要使用自定義名稱空間,as已經為我們準備好了appNs.

 xmlns:app="http://schemas.android.com/apk/res-auto"

而且這個空間是可以與其他控制元件公用的

<資料來源 android群英傳>