喜欢我的文章或是
觉得对自己有帮助
可以关注我的微信公众号:
【Android程序员日记】

前言

继之前的扩展式自定义view之后,我们再一起看看复合式自定义View,所谓复合式自定义View就是将我们平时用到一些单个的view组合起来,并对该组合控件进行属性自定义、暴露交互接口等,来实现我们的复合式自定义view!比如我们再这里我们用到的例子TopBar,就是利用两个button和一个TextView组合而成的自定义控件!
这次我们真的先来看一下别人的效果图:

该图是一个聊天软件的topbar


该图是一个时间管理软件的topbar

当我们在一个应用中会多次使用到这样一个控件组合的时候,我们就可以将他们直接做成我们复合式自定义控件,这样更方面我们绘制以及更新样式,统一风格!

正文

说多了都是空话,下面我们就来一起干吧!

1. 在attrs中定义属性文件

    
    //定义topbar相应的属性
    <resources>
        <declare-styleable name="TopBar">
            <attr name="myTitle" format="string"/>
            <attr name="myTitleTextSize" format="dimension"/>
            <attr name="myTitleTextColor" format="color"/>
            <attr name="leftTextColor" format="color"/>
            <attr name="leftBackground" format="reference|color"/>
            <attr name="leftText" format="string"/>
            <attr name="rightTextColor" format="color"/>
            <attr name="rightBackground" format="reference|color"/>
            <attr name="rightText" format="string"/>
           </declare-styleable>
    </resources>

2. 接下来我们创建一个类TopBar让它集成自ViewGroup,为了我们布局方便,在这里我们直接继承自ViewGroup的一个子类RelativieLayout,并实现里面的三个构造方法

    public class TopBar extends RelativeLayout {
        public TopBar(Context context) {
               this(context,null);
        }

        public TopBar(Context context, AttributeSet attrs) {
            this(context, attrs,0);

        }

        public TopBar(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
    
        }
    }

注:上面代码中有一个小技巧就是在一个参数中的构造方法中调用两个参数的构造方法,两个参数的构造方法中调用三个参数的构造方法,这样,我们就可以直接将所有要在构造方法中准备的内容都放在三个参数的构造方法中就好了,否则我们需要在每个构造方法中都实现一边

补: 在这里简单的对自定义view时出现的三个constructor进行简单的说明:

  • 一个参数的构造方法:在用代码动态的添加我们的自定义view时调用。

  • 两个参数的构造方法:在使用xml +inflate的方法添加控件时会调用,里面多了一个AttributeSet类型的值

  • 三个参数的构造方法:多了一个defStyleAttr参数,这是这个view引用style资源的属性参数!(暂且理解前面两个就好)

3. 获取这些属性对应的值组

    //前面在attr中定义过view的各种属性及其数据类型,我们这里就可以通过以下方法获取到xml中的对应属性值
    TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TopBar);
    mLeftBackgroud = ta.getDrawable(R.styleable.TopBar_leftBackground);
    mLeftText = ta.getString(R.styleable.TopBar_leftText);
    mLeftTextColor = ta.getColor(R.styleable.TopBar_leftTextColor, Color.BLACK);
    mTitleText = ta.getString(R.styleable.TopBar_myTitle);
    mTitleSize = ta.getDimension(R.styleable.TopBar_myTitleTextSize, 15);
    mTileTextColor = ta.getColor(R.styleable.TopBar_myTitleTextColor, Color.BLACK);
    mRightTextColor = ta.getColor(R.styleable.TopBar_rightTextColor, Color.BLACK);
    mRightText = ta.getString(R.styleable.TopBar_rightText);
    mRightBackground = ta.getDrawable(R.styleable.TopBar_rightBackground);
    //最后一定要调用recycle()来对ta进行回收,避免重新创建的错误
    ta.recycle();

注:上面的代码中颜色和大小的默认属性值,最好不要给0和和白色,如果你后面没有对这些属性进行重新复制,你就看不到你的字体,你会误以为自的控件出了问题。

4. 创建组成复合控件所需的单个控件,并将获取到的属性值设置给对应控件的属性

    mLeftButton = new Button(context);
    mRightButton = new Button(context);
    mTitleView = new TextView(context);

    //为创建的组建元素赋值
    //值就来源于我们引用的ml文件中给对应属性的赋值
    mLeftButton.setText(mLeftText);
    mLeftButton.setTextColor(mLeftTextColor);
    mLeftButton.setBackground(mLeftBackgroud);

    mRightButton.setBackground(mRightBackground);
    mRightButton.setText(mRightText);
    mRightButton.setTextColor(mRightTextColor);

    mTitleView.setText(mTitleText);
    mTitleView.setTextColor(mTileTextColor);
    mTitleView.setTextSize(mTitleSize);
    mTitleView.setGravity(Gravity.CENTER);

5. 设置控件的布局参数并添加控件

    //为每一个组件设置相应的布局参数
    mRightParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
    mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,TRUE);
    addView(mRightButton,mRightParams);

    mLeftParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
    mLeftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT,TRUE);
    addView(mLeftButton,mLeftParams);

    mTitleParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
    mTitleParams.addRule(RelativeLayout.CENTER_IN_PARENT,TRUE);
    addView(mTitleView,mTitleParams);

6. 定义并暴露接口(为了更好的交互效果,我们大多数自定义控件都要将相应的事件处理定义为借口暴露出去)

/**
 *该方法放在构造方法的合适位置调用,用来出触发我们的接口回调
 */
private void bindEvents() {
//按钮的点击事件不需要具体的实现,调用接口的时候,会有具体的实现
    mLeftButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            //为了提高代码的健壮性,我们需要在这里做不为null的判断
            if(mListener!=null){
                mListener.leftClick();
            }
        }
    });
    mRightButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            //为了提高代码的健壮性,我们需要在这里做不为null的判断
            if(mListener!=null){
                mListener.rightClick();
            }
        }
    });
}

/**
 ** 定义左右button点击的接口
 */
 public interface TopBarClickListener {
    //左边的点击事件
    void leftClick();
    //右边按钮的点击事件
    void rightClick();
}
/**
 ** 暴露一个接口供调用者来注册接口回调
 ** 通过接口来获得回调这对接口方法的实现
 */
 public void setOnTopbarClickListener(TopBarClickListener mListener){
    this.mListener=mListener;
}

补:为了更好的可自定义性,我们还可以给我们的各控件设置是否显示


/**
 ** 设置按钮的显示与否,通过id区分按钮,flag区分是否显示
 */
 public void setButtonVisable(int ChidView,boolean flag){
    switch (ChidView){
        case LEFT_BUTTUN:
            if (flag==true){
                mLeftButton.setVisibility(View.VISIBLE);
            }else{
                mLeftButton.setVisibility(View.INVISIBLE);
            }
            break;
        case RIGHT_BUTTUN:
            if (flag==true){
                mRightButton.setVisibility(View.VISIBLE);
            }else{
                mRightButton.setVisibility(View.INVISIBLE);
            }
            break;
    }
}

7. 引用UI模板

  1. 一定要先加下面这一行

        xmlns:custom="http://schemas.android.com/apk/res-auto"
  2. 像引用android其他控件一样引用我们的自定义控件

        <com.timen4.ronnny.topbar.widget.TopBar
        android:id="@+id/topBar"
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        custom:myTitle="美图浏览"
        custom:myTitleTextSize="15sp"
        custom:myTitleTextColor="@android:color/black"
        custom:rightText="下一张"
        custom:rightBackground="@android:color/holo_red_light"
        custom:leftText="上一张"
        custom:leftBackground="@android:color/holo_green_light"
        />

    注:一定要是TopBar的全类名

8. 实现接口回调

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mtopBar = (TopBar) findViewById(R.id.topBar);
    iv_image = (ImageView) findViewById(R.id.iv_image);
    initData();
    if (index==0){
        iv_image.setImageResource(images.get(0));
    }

    mtopBar.setButtonVisable(TopBar.RIGHT_BUTTUN,true);
    mtopBar.setButtonVisable(TopBar.LEFT_BUTTUN,true);

    //实现接口的主要代码
    mtopBar.setOnTopbarClickListener(new TopBar.TopBarClickListener() {
        @Override
        public void leftClick() {
            Toast.makeText(MainActivity.this,"上一张",Toast.LENGTH_SHORT).show();
            if (index<=0){
                index=3;
            }else{
                index--;
            }
            iv_image.setImageResource(images.get(index));
        }

        @Override
        public void rightClick() {
            Toast.makeText(MainActivity.this,"下一张",Toast.LENGTH_SHORT).show();
            if (index==3){
                index=0;
            }else{
                index++;
            }
            iv_image.setImageResource(images.get(index));
        }
    });
}
//将图片资源放到我们的结合中
private void initData() {
    images = new ArrayList<>();
    images.add(0,R.drawable.a);
    images.add(0,R.drawable.b);
    images.add(0,R.drawable.c);
    images.add(0,R.drawable.d);
}

最后我们看一下,我们完成之后的效果(主要是看上面的横条哈,咳咳!):

后记

最后总还是应该在说点什么!作为一名程序员,代码总是要敲的,在敲的过程中,其实就是在着实建造我们大脑里的王国!不建造出来,构想的再美也没有用!对于一些再复杂一点的控件而言。这里有一句话与君共勉:

千里之行,始于足下!

我的github源码:https://github.com/luorenyu/TopBar.git


欢迎扫码关注我的微信公众号


贤榆的榆
84 声望12 粉丝