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
ViewGroupB
ViewC

那么一个触碰事件,的触发顺序是

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群英传>

猜你喜欢

转载自blog.csdn.net/RungBy/article/details/83346377
今日推荐