Android 自定义复合控件之通用标题栏

来源:互联网 发布:java action定义 编辑:程序博客网 时间:2024/05/24 22:42

版权声明:本文为博主原创文章,未经博主允许不得转载。

效果图

估计大家应该和我一样,每次去看别人博客的时候,都喜欢一拉到底,先看看有没有效果图,符不符合自己的需求,符合咱就继续看,不符合免得浪费表情,所以效果图先上为敬
这里写图片描述

写在前面的一点儿废话

作为Android的菜鸟一枚,一直觉得能够写自定义控件是一个很炫酷的技能,最近看了徐宜生老师的群英传之后,感觉收获还是挺多的。这篇文章就主要记录的是学习自定义控件中最简单的复合控件的过程。虽然现在MD中Toolbar已经完全满足各种各样的需求,但对于我这种菜鸟来说自己动手写一个还是能学到很多东西的!

1、自定义控件的属性

既然是自定义的控件,肯定得提供属性选项,以方便实现不同的样式。提供自定义的属性是很简单的,在res资源目录下的values目录下创建一个attrs.xml的属性集定义的xml文件,在该文件中自定义各种必要的属性

<?xml version="1.0" encoding="utf-8"?><resources>    <declare-styleable name="NormalTopBar">        <!--中间标题属性-->        <attr name="titleText" format="string"/>        <attr name="titleTextSize" format="dimension"/>        <attr name="titleTextColor" format="color"/>        <attr name="leftText" format="string"/>        <!--左边按钮属性-->        <attr name="leftTextSize" format="dimension"/>        <attr name="leftTextColor" format="color"/>        <attr name="leftImageSrc" format="reference"/>        <attr name="rightText" format="string"/>        <!--右边按钮属性-->        <attr name="rightTextSize" format="dimension"/>        <attr name="rightTextColor" format="color"/>        <attr name="leftBackground" format="color"/>        <attr name="rightBackground" format="color"/>        <attr name="rightImageSrc" format="reference"/>    </declare-styleable></resources>

既然自定义了属性,就需要在自定义控件模板中去获取这些属性的赋值,以处理得到相应的显示效果。在这里,系统提供了TypeArray类,获取到该类的实例后就可通过getString()等方法获得布局文件中设置的属性值

 private void getTypeArray(Context context, AttributeSet attrs) {        //将attrs.xml中定义的属性存储到TypeArray中        TypedArray typeArray=context.obtainStyledAttributes(attrs,R.styleable.NormalTopBar);        leftText=typeArray.getString(R.styleable.NormalTopBar_leftText);        leftTextColor=typeArray.getColor(R.styleable.NormalTopBar_leftTextColor, Color.BLACK);        leftTextSize=typeArray.getDimension(R.styleable.NormalTopBar_leftTextSize,12);        leftImageId=typeArray.getResourceId(R.styleable.NormalTopBar_leftImageSrc,0);        titleText=typeArray.getString(R.styleable.NormalTopBar_titleText);        titleTextColor=typeArray.getColor(R.styleable.NormalTopBar_titleTextColor,Color.BLACK);        titleTextSize=typeArray.getDimension(R.styleable.NormalTopBar_titleTextSize,20);        rightText=typeArray.getString(R.styleable.NormalTopBar_rightText);        rightTextColor=typeArray.getColor(R.styleable.NormalTopBar_rightTextColor,Color.BLACK);        rightTextSize=typeArray.getDimension(R.styleable.NormalTopBar_rightTextSize,12);        rightImageId=typeArray.getResourceId(R.styleable.NormalTopBar_rightImageSrc,0);        typeArray.recycle();//获取完所有属性后需要调用recycle来避免重新创建发生的错误    }

参数中attrs是控件构造函数中传入的属性集参数,而R.styleable.NormalTopBar就是在attrs.xml文件中定义的该控件属性集的名字。

2、动态添加控件组合成自定义符合控件

标题栏中一般包括了左边的按钮,中间的标题,右边的按钮。在本文中,我把该控件分成了5个部分,左边有一个ImageView和一个TextView用于用户点击,中间有一个TextView用于显示标题,右边和左边一样,成对称分布,然后这些控件的父控件是RelativeLayout,方便子控件的布局。了解了有哪些控件之后,就可以初始化这些控件对象,然后分别指定合适的布局,动态添加布局中。

 private void addAllView(Context context) {        leftTextView =new TextView(context);        rightTextView =new TextView(context);        titleTextView=new TextView(context);        leftImage=new ImageView(context);        rightImage=new ImageView(context);        leftImage.setId(R.id.leftimageid);        leftImage.setImageResource(leftImageId);        //leftImage.setAdjustViewBounds(true);        leftTextView.setText(leftText);        leftTextView.setTextSize(leftTextSize);        leftTextView.setTextColor(leftTextColor);        titleTextView.setText(titleText);        titleTextView.setTextSize(titleTextSize);        titleTextView.setTextColor(titleTextColor);        titleTextView.setGravity(Gravity.CENTER);//一定要设置textview内容的位置        rightTextView.setText(rightText);        rightTextView.setTextSize(rightTextSize);        rightTextView.setTextColor(rightTextColor);        rightImage.setId(R.id.rightimageid);        rightImage.setImageResource(rightImageId);        //为组建设置相应的布局        if(leftImageId!=0&&leftText!=null){            leftImageParams=new LayoutParams(dpToPx(context,35), dpToPx(context,35));            leftImageParams.addRule(ALIGN_PARENT_LEFT,TRUE);            leftImageParams.addRule(CENTER_VERTICAL,TRUE);            addView(leftImage,leftImageParams);            leftTextParams =new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);            leftTextParams.addRule(RelativeLayout.RIGHT_OF,R.id.leftimageid);            leftTextParams.addRule(CENTER_VERTICAL,TRUE);            leftTextView.setGravity(Gravity.LEFT);            addView(leftTextView, leftTextParams);        }else if(leftImageId!=0&&leftText==null){            leftImageParams=new LayoutParams(dpToPx(context,35), dpToPx(context,35));            leftImageParams.addRule(ALIGN_PARENT_LEFT,TRUE);            leftImageParams.addRule(CENTER_VERTICAL,TRUE);            addView(leftImage,leftImageParams);        }else{            leftTextParams =new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);            leftTextParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT,TRUE);            leftTextParams.addRule(CENTER_VERTICAL,TRUE);            addView(leftTextView, leftTextParams);        }        titleParams=new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);        titleParams.addRule(CENTER_IN_PARENT,TRUE);        titleParams.addRule(TEXT_ALIGNMENT_CENTER);        addView(titleTextView,titleParams);        if(rightImageId!=0&&rightText!=null){            rightTextParams =new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);            rightTextParams.addRule(RelativeLayout.LEFT_OF,R.id.rightimageid);            rightTextParams.addRule(RelativeLayout.CENTER_VERTICAL,TRUE);            rightTextView.setGravity(Gravity.RIGHT);            addView(rightTextView, rightTextParams);            rightImageParams=new LayoutParams(dpToPx(context,35), dpToPx(context,35));            rightImageParams.addRule(CENTER_VERTICAL,TRUE);            rightImageParams.addRule(ALIGN_PARENT_RIGHT,TRUE);            addView(rightImage,rightImageParams);        }else if(rightImageId!=0&&rightText==null){            rightImageParams=new LayoutParams(dpToPx(context,35), dpToPx(context,35));            rightImageParams.addRule(CENTER_VERTICAL,TRUE);            rightImageParams.addRule(ALIGN_PARENT_RIGHT,TRUE);            addView(rightImage,rightImageParams);        }else{            rightTextParams =new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);            rightTextParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,TRUE);            rightTextParams.addRule(RelativeLayout.CENTER_VERTICAL,TRUE);            addView(rightTextView, rightTextParams);        }    }

这一段代码首先是初始化得到各子控件的实例对象,然后将属性值赋值给对应的控件,接着利用LayoutParams类对各子空间的大小和位置进行设定,最后利用addView方法即可将这些子控件添加到控件整体布局中。

这段代码中,主要的难点在于运用LayoutParams,要注意该布局的外层viewGroup是RelativeLayout,所以在定义和初始化的时候都需要使用RelativeLayout.LayoutParams.另外LayoutParams的构造函数中的参数用于控制大小,我在设置ImageView对应的LayoutParams时,最开始把宽和高都设置为WRAP_CONTENT,但是运行后效果不理想,imageview宽度占据了一半的空间,最后决定对该控件的大小指定尺寸大小,不过要注意构造函数中的数值单位是px,所以需要先定义一个函数将dp转为px再赋值给构造函数。

这段代码的另外一个难点是,当我两侧的按钮同时有文字和图标时,对于ImageView和TextView的定位是个问题。在下面代码中

leftTextParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT,TRUE);

这行代码将TextView定位在了父控件的左侧,而当左侧同时有ImageView和TextView时二者肯定就会重叠,这肯定不是想要的结果。所以需要把ImageView仍定位在最左边,然后TextView定位在前者的右边,而在方法addRule()中,可以使用 addRule(RelativeLayout.RIGHT_OF,int view) 来把对应的控件定位在参数中view控件的右边,但是该参数需要的是资源ID,可问题是在上面我们是动态添加的ImageView,并没有在xml文件中定义id。我尝试了直接用imageview.getId(),但得到的结果经调试发现是-1,并不能实现想要的效果,最后一搜找到了一个方法,首先在资源目录res下的values下再新建一个ids.xml的文件,然后在文件中定义一个类型为id的item

<?xml version="1.0" encoding="utf-8"?><resources>    <item name="leftimageid" type="id"/>    <item name="rightimageid" type="id"/></resources>

然后利用ImageView.setId(R.id.leftimageid) 就能给动态添加的控件赋值一个不会与其他资源id重复的id,接着就可以在布局中使用。

3、定义接口暴露给调用者

到目前位置,编写的自定义控件已经可以在xml布局文件中使用,而且也能在界面上显示出来,但是左右两侧的按钮点击事件对于不同的使用者或者不同的页面,所要完成的动作肯定是不一样的,所以得暴露一个接口给调用者自己去实现。

public interface normalTopClickListener{        void onLeftClick(View view);        void onRightClick(View view);    }

然后给调用者提供一个set函数让调用者来实现该接口中的方法

 public void setTopClickListener(normalTopClickListener mListener){        this.mClickListener =mListener;    }

最后在控件模板中,在左右控件的点击事件里去调用接口的方法,即可得到调用者的具体实现

private void addOnClick() {        leftTextView.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View view) {                mClickListener.onLeftClick(view);            }        });        leftImage.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View view) {                mClickListener.onLeftClick(view);            }        });        rightTextView.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View view) {                mClickListener.onRightClick(view);            }        });        rightImage.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View view) {                mClickListener.onRightClick(view);            }        });    }

github源码

结语

终于写完了第一篇博客,说句实在的,第一次写起来感觉真不简单。如果文中有任何错误或者建议,欢迎指出,不胜感激

2017.3.20 23:45
806实验室

0 0
原创粉丝点击