这一定是最简单的自定义布局

说明

上半部分下载按钮为主页面

下半部分进度条和取消按钮为引用的自定义布局

这一定是最简单的自定义布局_第1张图片

简介

啥?自定义 View 和自定义布局不一样?没错,自定义 View 是画布局,重在画,从 0 到 1;自定义布局是组合控件,重在组合,将分散的多个控件组合成一个整体。所以意义上是不一样的,当然广义上都可以叫自定义 View,毕竟殊途同归。这里对 View 不做介绍,需要了解的可以参考这篇文章进阶之路-自定义View(公众号文章,见文末)

思路

需求:

外部类可以在代码里和xml里分别设置进度条的最大值(max),进度值(progress)

整体思路:

代码设置需求可以提供外部类三个方法:setMax(),setProgress() 和 setBtnCancelListener()。setBtnCancelListener 用来取消下载监听(免费赠送的)

xml 设置需求可以通过 attrs.xml 自定义属性实现

实现分三大步:

一:自定义布局动态设置属性(代码需求)

二:自定义布局静态设置属性(xml需求,若没有xml需求这一步可以省略)

三:外部类调用

实现

第一步,自定义布局动态设置属性。我们使用某个控件一般都需要在代码里动态设置其属性或者方法,所以现在先说这一步的实现。分为4小步

1. 新建布局。本例中是一个ProgressBar+Button,和平时写的没有任何区别,放在layout文件夹下即可

xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:app="http://schemas.android.com/apk/res-auto"

android:layout_width="match_parent"

android:layout_height="match_parent">

android:id="@+id/pbar_progress"

style="?android:attr/progressBarStyleHorizontal"

android:layout_width="0dp"

android:layout_height="wrap_content"

app:layout_constraintBottom_toBottomOf="parent"

app:layout_constraintLeft_toLeftOf="parent"

app:layout_constraintRight_toRightOf="parent"

app:layout_constraintTop_toTopOf="parent"/>

android:id="@+id/btn_cancel"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="取消"

app:layout_constraintBottom_toBottomOf="parent"

app:layout_constraintLeft_toLeftOf="parent"

app:layout_constraintRight_toRightOf="parent"

app:layout_constraintTop_toBottomOf="@id/pbar_progress"/>


效果图如下:

这一定是最简单的自定义布局_第2张图片

2. 新建自己的View类。新建一个类继承LinearLayout并重写下面两个构造方法,为啥要继承线性布局呢,你喜欢的话也可以继承相对布局,建议先学会再研究

/**

* 自定义组件

* Created by wangjiong on 2017/12/18.

*/

publicclassCompoundViewextendsLinearLayout{

publicCompoundView(Context context){

super(context);

}

publicCompoundView(Context context, @Nullable AttributeSet attrs){

super(context, attrs);

}

}

3. 关联布局。可以看到我们通过LayoutInflater类将第 1 步中的布局转化为view,然后通过这个view获取到里面的控件,并在两个构造方法中都实现了此步骤

/**

* 自定义组件

* Created by wangjiong on 2017/12/18.

*/

publicclassCompoundViewextendsLinearLayout{

privateProgressBar pbar;

privateButton btnCancel;

publicCompoundView(Context context){

super(context);

// 初始化布局

initView(context);

}

publicCompoundView(Context context, @Nullable AttributeSet attrs){

super(context, attrs);

// 初始化布局

initView(context);

}

/**

* 初始化布局

*/

privatevoidinitView(Context context){

// 将布局导入到LinearLayout中

View view = LayoutInflater.from(context).inflate(R.layout.view_compound,this);

pbar = (ProgressBar) view.findViewById(R.id.pbar_progress);

btnCancel = (Button) view.findViewById(R.id.btn_cancel);

}

}

4. 设置供外部类调用的方法。可以看到除了 onFinishInflate() 方法之外,新增的 setMax(int max),setProgress(int progress) 和 setBtnCancelListener(OnClickListener lickListener) 这三个方法应该没啥难度。那么重写的onFinishInflate()是干嘛的呢?就像注释写的那样,官方话:子控件均被映射成XML文件才触发的。说人话:在xml布局中静态设置属性值之后,在外部类动态设置 setMax(int max) 等方法之前调用。所以如果你没有自定义属性,那么这个方法完全可以不重写。其实到这里已经实现了自定义布局,外部类的布局中可以正常使用这个CompoundView ,外部类的代码中可以正常调用CompoundView 定义的方法

/**

* 自定义组件

* Created by wangjiong on 2017/12/18.

*/

publicclassCompoundViewextendsLinearLayout{

privateProgressBar pbar;

privateButton btnCancel;

privateintmax =100;// 最大进度(这里默认为100,自己可以随便设置,看业务)

privateintprogress =0;// 当前进度(这里默认为0,自己可以随便设置,看业务)

/**

* 设置最大值

*

*@parammax 最大值

*/

publicvoidsetMax(intmax){

this.max = max;

pbar.setMax(max);

}

/**

* 设置当前进度

*

*@paramprogress 进度

*/

publicvoidsetProgress(intprogress){

if(progress > max) {

this.progress = max;

}else{

this.progress = progress;

}

pbar.setProgress(this.progress);

}

publicvoidsetBtnCancelListener(OnClickListener lickListener){

btnCancel.setOnClickListener(lickListener);// 给取消按钮设置监听(这里也可以用接口回调供外部类调用)

}

/**

* 子控件均被映射成XML文件才触发的(在xml静态设置属性之后,动态设置属性之前)

*/

@Override

protectedvoidonFinishInflate(){

super.onFinishInflate();

pbar.setMax(max);

pbar.setProgress(progress);

}

publicCompoundView(Context context){

super(context);

// 初始化布局

initView(context);

}

publicCompoundView(Context context, @Nullable AttributeSet attrs){

super(context, attrs);

// 初始化布局

initView(context);

}

/**

* 初始化布局

*/

privatevoidinitView(Context context){

// 将布局导入到LinearLayout中

View view = LayoutInflater.from(context).inflate(R.layout.view_compound,this);

pbar = (ProgressBar) view.findViewById(R.id.pbar_progress);

btnCancel = (Button) view.findViewById(R.id.btn_cancel);

}

}

第二步,自定义布局静态设置属性。分2小步

1. 新建 attrs.xml 布局自定义属性。在 res/values 下新建 attrs.xml 文件,declare-styleable 对标签的 name 要和自定义类名相同,attr 对标签的 name 可以随便写,简单易懂即可,format 值为对应的数据类型,这里我们根据业务设置了 integer 类型


2. 自定义类里初始化自定义属性。在第二个构造方法中多出了 4 行代码,这 4 行代码的作用就是将你引用此自定义布局时在xml设置的值取出来,然后分别赋给对应的成员变量

/**

* 自定义组件

* Created by wangjiong on 2017/12/18.

*/

publicclassCompoundViewextendsLinearLayout{

privateProgressBar pbar;

privateButton btnCancel;

privateintmax =100;// 最大进度(这里默认为100,自己可以随便设置,看业务)

privateintprogress =0;// 当前进度(这里默认为0,自己可以随便设置,看业务)

/**

* 设置最大值

*

*@parammax 最大值

*/

publicvoidsetMax(intmax){

this.max = max;

pbar.setMax(max);

}

/**

* 设置当前进度

*

*@paramprogress 进度

*/

publicvoidsetProgress(intprogress){

if(progress > max) {

this.progress = max;

}else{

this.progress = progress;

}

pbar.setProgress(this.progress);

}

publicvoidsetBtnCancelListener(OnClickListener lickListener){

btnCancel.setOnClickListener(lickListener);// 给取消按钮设置监听(这里也可以用接口回调供外部类调用)

}

/**

* 子控件均被映射成XML文件才触发的(在xml静态设置属性之后,动态设置属性之前)

*/

@Override

protectedvoidonFinishInflate(){

super.onFinishInflate();

pbar.setMax(max);

pbar.setProgress(progress);

}

publicCompoundView(Context context){

super(context);

// 初始化布局

initView(context);

}

publicCompoundView(Context context, @Nullable AttributeSet attrs){

super(context, attrs);

// 初始化布局

initView(context);

// 自定义布局静态设置属性。这一步会将你引用此自定义布局时在xml设置的值取出来

TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CompoundView);

max = typedArray.getInt(R.styleable.CompoundView_max,100);

progress = typedArray.getInt(R.styleable.CompoundView_progress,10);

typedArray.recycle();

}

/**

* 初始化布局

*/

privatevoidinitView(Context context){

// 将布局导入到LinearLayout中

View view = LayoutInflater.from(context).inflate(R.layout.view_compound,this);

pbar = (ProgressBar) view.findViewById(R.id.pbar_progress);

btnCancel = (Button) view.findViewById(R.id.btn_cancel);

}

}

第三步,外部类调用。以上两大步咱们的自定义布局就已经完成了,接着用外部类调用的方式测试一下效果。很简单,一个点击下载的按钮,一个自定义类的引用。

其中命名空间

xmlns:wj="http://schemas.android.com/apk/res-auto"

这个命名空间可以自定义名字,这里我用的 wj,如果你用了app,那么ConstraintLayout类里只需要一个就行啦。

xmlns:app="http://schemas.android.com/apk/res-auto"

友情提示,咱们自定义的属性在外部使用的时候需要命名空间才能使用哦,就像安卓自己的属性在使用时同样需要加命名空间,然后属性前缀为android:

xmlns:android="http://schemas.android.com/apk/res/android"

外部类的布局里验证。wj:max 和 wj:progress 为自定义属性


xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:app="http://schemas.android.com/apk/res-auto"

xmlns:tools="http://schemas.android.com/tools"

xmlns:wj="http://schemas.android.com/apk/res-auto"

android:layout_width="match_parent"

android:layout_height="match_parent"

tools:context="com.wj.test.activity.CustomWidgetActivity">

android:id="@+id/btn_down"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="下载"

app:layout_constraintLeft_toLeftOf="parent"

app:layout_constraintRight_toRightOf="parent"

app:layout_constraintTop_toTopOf="parent"/>


android:id="@+id/cv_progress"

android:layout_width="0dp"

android:layout_height="wrap_content"

app:layout_constraintBottom_toBottomOf="parent"

app:layout_constraintLeft_toLeftOf="parent"

app:layout_constraintRight_toRightOf="parent"

app:layout_constraintTop_toBottomOf="@id/btn_down"

wj:max="50"

wj:progress="5"/>

外部类的代码中验证。如果同时在xml里和代码里设置属性,那么代码的设置会覆盖掉xml的,常识哦

/**

* 外部类测试自定义控件

* Created by wj on 2017/12/19 17:05

*/

publicclassCustomWidgetActivityextendsAppCompatActivity{

privateCompoundView view;// 自定义控件对象

privatebooleanisNotFinish =true;// 是否结束

privateinti;

@Override

protectedvoidonCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_custom_widget);

view = (CompoundView) findViewById(R.id.cv_progress);

view.setMax(100);// 设置最大值

view.setProgress(90);// 设置当前进度

view.setBtnCancelListener(newView.OnClickListener() {// 取消监听

@Override

publicvoidonClick(View v){

isNotFinish =false;

i =0;

view.setProgress(i);

}

});

Button down = (Button) findViewById(R.id.btn_down);

down.setOnClickListener(newView.OnClickListener() {// 下载监听

@Override

publicvoidonClick(finalView v){

isNotFinish =true;

// 子线程模拟进度

newThread() {

@Override

publicvoidrun(){

while(isNotFinish && i <=100) {

try{

Thread.sleep(1000);

// 主线程更新UI

runOnUiThread(newRunnable() {

@Override

publicvoidrun(){

view.setProgress(i++);

}

});

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

}.start();

}

});

}

}

全剧终

源码地址:https://github.com/GodJiong/MyApplication

这一定是最简单的自定义布局_第3张图片

你可能感兴趣的:(这一定是最简单的自定义布局)