自定义View之复合式

前言

继之前的扩展式自定义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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,378评论 25 707
  • 参考文章。http://blog.csdn.net/xmxkf/article/details/51468648h...
    codeHoward阅读 1,065评论 0 4
  • 这是自我任教以来,放得最长的一个寒假。 明天就收假了,回想了一下,整个假期,似乎没做什么有意义的事。我竟就这样...
    兰不言阅读 340评论 0 1
  • 很多人可能並不會想到 嚴陣以待下的隊列里 其實是一一張不同而獨立的臉 Photo by Joseph Chung
    憨憨爹阅读 93评论 0 0
  • 下午两点钟的秋天的阳光,照在身上竟没有太多的暖意,风过后,更多的是寒冷>_<。 等待究竟是怎么样的呢,我坐在这里,...
    哟AA喔阅读 134评论 0 0