我们要用UI模板的时候,如果所有的Topbar内容都是没有变的话,那我们用在xml文件中include进去就好了,但是如果Topbar中的内容是会随着fragment或者activity改变的话,拿我们总不能每次都去写多个文件,再分别include进去,这样的话就又变成还不如每次重写了,所以这种情况就要用到自定义的模板了,我们打造应该属于自己的控件就好了。但是怎么做呢,我们就来看看Android系统对于它的控件是怎么做的吧:比如我们要使用LinearLayout的时候我们会发现有android:layout_width="match_parent"
这类属性,但是当我们点开LinearLayout的时候,会弹出下面这个文件,并且我们在这个路径下可以看到一系列的定义中就有layout_width,这样我们就能明白系统是怎么定义控件的了。
然后我们就可以仿造系统的样式来写我们自己的自定义UI模板,接下来我们就用一个自定义UI模板的例子来说下过程吧。
我们先在新建res中一个atts.xml文件,然后和系统上一样定义自己要的属性就好了。
<resurces>
<declare-styleable name="Topbar">
<attr name="title" format="string"/>
<attr name="titleTextSize" format="dimension"/>
<attr name="titleTextColor" format="color"/>
<attr name="leftText" format="string"/>
<attr name="leftBackground" format="color|reference"/>
<attr name="leftTextColor" format="color"/>
<attr name="rightText" format="string"/>
<attr name="rightBackground" format="color|reference"/>
<attr name="rightTextColor" format="color"/>
declare-styleable>
resources>
其实我们就是仿照系统的写法把你想要的属性名字以及你限定的属性只能包含的数据类型规定好。这样我们就把自定义属性给写好了,然后我们就要和系统一样,开始写自定义View。
我们在atts.xml中定义的declare-styleable name="Topbar"
Topbar这个名字,我们新建一个Topbar.java文件,因为我们想要的是一个有返回按钮以及菜单按钮的Topbar,这样就需要布局,所以我们继承于RelativeLayout,然后我们在java文件中开始编写逻辑代码了,使得我们的Topbar能达到我们想要的效果
package com.gin.xjh.testfromtopbar;
public class Topbar extends RelativeLayout {
public Topbar(final Context context, AttributeSet attrs) {
super(context, attrs);
}
}
然后我们就要开始把我们想要的控件以及属性给声明一下:
//定义控件
private Button leftButton,rightButton;
private TextView Title;
//定义属性
private int leftTextColor;
private Drawable leftBackground;
private String leftText;
private int rightTextColor;
private Drawable rightBackground;
private String rightText;
private String title;
private float titleTextSize;
private int titleTextColor;
之后我们就要开始把属性值从之后调用的Topbar中取出来,这样我们要怎么做呢?其实我们的构造方法传递回来了一个值AttributeSet attrs,只要我们在构造方法中把attrs和Topbar的属性名构造成TypedArray,这个里面就存储了我们之后调用的Topbar中我们对它赋的值,然后我们只要把他取出来然后给控件赋这个值就好了。其实TypedArray和C++中的map一样,都是以键值对的形式进行存储的,然后我们用什么名字进行查询呢?其实Android帮我们做好了,我们只要把控件名+'_'+属性名
传入进去就好了,我们最后一点要记得把TypedArray释放掉,要不然就会一直占用系统资源,然后我们把所有控件都new出来,然后调用set方法设置我们之前取到的值就好了。
TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.Topbar);//TypedArray存储我们自定义XML中的属性值,TypedArray是通过键值对存储的
leftTextColor = ta.getColor(R.styleable.Topbar_leftTextColor,0);//第二个是默认值
leftBackground = ta.getDrawable(R.styleable.Topbar_leftBackground);
leftText = ta.getString(R.styleable.Topbar_leftText);
rightTextColor = ta.getColor(R.styleable.Topbar_rightTextColor,0);
rightBackground = ta.getDrawable(R.styleable.Topbar_rightBackground);
rightText = ta.getString(R.styleable.Topbar_rightText);
title = ta.getString(R.styleable.Topbar_title);
titleTextSize = ta.getDimension(R.styleable.Topbar_titleTextSize,0);
titleTextColor = ta.getColor(R.styleable.Topbar_titleTextColor,0);
ta.recycle();//释放资源
//初始化控件
leftButton = new Button(context);
rightButton = new Button(context);
Title = new TextView(context);
//给控件赋属性值
leftButton.setTextColor(leftTextColor);
leftButton.setBackground(leftBackground);
leftButton.setText(leftText);
rightButton.setTextColor(rightTextColor);
rightButton.setBackground(rightBackground);
rightButton.setText(rightText);
Title.setText(title);
Title.setTextColor(titleTextColor);
Title.setTextSize(titleTextSize);
Title.setGravity(Gravity.CENTER);
setBackgroundColor(0xFFF59563);
然后我们就要想怎么办把控件放入ViewGroup中呢?要把一个控件放入Layout中去就要用到一个属性LayoutParams,LayoutParams继承于Android.View.ViewGroup.LayoutParams相当于一个Layout的信息包,它封装了Layout的位置、高、宽等信息。假设在屏幕上一块区域是由一个Layout占领的,如果将一个View添加到一个Layout中,最好告诉Layout用户期望的布局方式,也就是将一个认可的layoutParams传递进去。因为我们有三个不同的部分,所以我们要三个LayoutParams:
private LayoutParams leftParams,rightParams,titleParams;
然后把控件所要的空间以及位置设置进去就好了。
leftParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
leftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT,TRUE);
addView(leftButton,leftParams);
rightParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
rightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,TRUE);
addView(rightButton,rightParams);
titleParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);
titleParams.addRule(RelativeLayout.CENTER_IN_PARENT,TRUE);
addView(Title,titleParams);
这样我们就能把控件设置进去了
控件我们想要使用的话,对于View我们要实现他的点击事件,比如现在这个例子我们肯定要把两个Button的点击事件实现,但是可能有的人会说我们直接在Topbar的文件中定义好了就不行了嘛,但是这样的确解决了这个问题,但是这样由做不到复用性的问题了,这下我们就又要参考系统中对于点击事件是怎么实现的,其实我们就可以发现其实他是用到了接口回调的方法,暴露出一个方法让Button进行调用,然后传入一个接口,在接口中的方法编写我们想要的方法,我们就用这个方法来实现我们自定义点击事件:
我们先编写一个接口:
public interface topbarClickListener{
void leftClick();
void rightClick();
}
然后我们也编写一个类似setOnClickListener()的方法,要求他要传入我们之前写的那个接口:
private topbarClickListener listener;
public void setOnTopbarClickListener(topbarClickListener listener){
this.listener=listener;
}
然后我们在构造方法中的Button调用他所要实现的方法就好了。
leftButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
listener.leftClick();
}
});
rightButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
listener.rightClick();
}
});
这样我们就可以在MainActivity中调用这个就好了,然后通过接口回调让他自动执行点击事件。
topbar=findViewById(R.id.topbar);
topbar.setOnTopbarClickListener(new Topbar.topbarClickListener() {
@Override
public void leftClick() {
Toast.makeText(MainActivity.this,"LEFT",Toast.LENGTH_SHORT).show();
}
@Override
public void rightClick() {
Toast.makeText(MainActivity.this,"RIGHT",Toast.LENGTH_SHORT).show();
}
});
这个就有很多了,比如如果没有什么条件就不显示控件之类的,或者滑动某个控件会这样之类的,我们就拿从控件不要显示来说吧。我们只要在Topbar.java中定义一个方法就好了。
public void setLeftIsVisable(boolean flag){
if(flag){
leftButton.setVisibility(View.VISIBLE);
}
else{
leftButton.setVisibility(View.GONE);
}
}
然后我们在Activity中调用这个方法传入false就好了
topbar.setLeftIsVisable(false);