說明
-
上半部分下載按鈕爲主頁面
-
下半部分進度條和取消按鈕爲引用的自定義佈局
簡介
啥?自定義 View 和自定義佈局不一樣?沒錯,自定義 View 是畫布局,重在畫,從 0 到 1;自定義佈局是組合控件,重在組合,將分散的多個控件組合成一個整體。所以意義上是不一樣的,當然廣義上都可以叫自定義 View,畢竟殊途同歸。這裏對 View 不做介紹,需要了解的可以參考這篇文章進階之路-自定義View
思路
需求:
-
外部類可以在代碼裏和xml裏分別設置進度條的最大值(max),進度值(progress)
整體思路:
-
代碼設置需求可以提供外部類三個方法:setMax(),setProgress() 和 setBtnCancelListener()。setBtnCancelListener 用來取消下載監聽(免費贈送的)
-
xml 設置需求可以通過 attrs.xml 自定義屬性實現
實現分三大步:
一:自定義佈局動態設置屬性(代碼需求)
二:自定義佈局靜態設置屬性(xml需求,若沒有xml需求這一步可以省略)
三:外部類調用
實現
第一步,自定義佈局動態設置屬性。我們使用某個控件一般都需要在代碼裏動態設置其屬性或者方法,所以現在先說這一步的實現。分爲4小步
1. 新建佈局。本例中是一個ProgressBar+Button,和平時寫的沒有任何區別,放在layout文件夾下即可
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
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">
<ProgressBar
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"/>
<Button
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"/>
</android.support.constraint.ConstraintLayout>
效果圖如下:
2. 新建自己的View類。新建一個類繼承LinearLayout並重寫下面兩個構造方法,爲啥要繼承線性佈局呢,你喜歡的話也可以繼承相對佈局,建議先學會再研究
/**
* 自定義組件
* Created by wangjiong on 2017/12/18.
*/
public class CompoundView extends LinearLayout {
public CompoundView(Context context) {
super(context);
}
public CompoundView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
}
3. 關聯佈局。可以看到我們通過LayoutInflater類將第 1 步中的佈局轉化爲view,然後通過這個view獲取到裏面的控件,並在兩個構造方法中都實現了此步驟
/**
* 自定義組件
* Created by wangjiong on 2017/12/18.
*/
public class CompoundView extends LinearLayout {
private ProgressBar pbar;
private Button btnCancel;
public CompoundView(Context context) {
super(context);
// 初始化佈局
initView(context);
}
public CompoundView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
// 初始化佈局
initView(context);
}
/**
* 初始化佈局
*/
private void initView(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.
*/
public class CompoundView extends LinearLayout {
private ProgressBar pbar;
private Button btnCancel;
private int max = 100;// 最大進度(這裏默認爲100,自己可以隨便設置,看業務)
private int progress = 0;// 當前進度(這裏默認爲0,自己可以隨便設置,看業務)
/**
* 設置最大值
*
* @param max 最大值
*/
public void setMax(int max) {
this.max = max;
pbar.setMax(max);
}
/**
* 設置當前進度
*
* @param progress 進度
*/
public void setProgress(int progress) {
if (progress > max) {
this.progress = max;
} else {
this.progress = progress;
}
pbar.setProgress(this.progress);
}
public void setBtnCancelListener(OnClickListener lickListener) {
btnCancel.setOnClickListener(lickListener);// 給取消按鈕設置監聽(這裏也可以用接口回調供外部類調用)
}
/**
* 子控件均被映射成XML文件才觸發的(在xml靜態設置屬性之後,動態設置屬性之前)
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
pbar.setMax(max);
pbar.setProgress(progress);
}
public CompoundView(Context context) {
super(context);
// 初始化佈局
initView(context);
}
public CompoundView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
// 初始化佈局
initView(context);
}
/**
* 初始化佈局
*/
private void initView(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 類型
<resources>
<!--自定義控件-->
<declare-styleable name="CompoundView">
<attr name="max" format="integer"/>
<attr name="progress" format="integer"/>
</declare-styleable>
</resources>
2. 自定義類裏初始化自定義屬性。在第二個構造方法中多出了 4 行代碼,這 4 行代碼的作用就是將你引用此自定義佈局時在xml設置的值取出來,然後分別賦給對應的成員變量
/**
* 自定義組件
* Created by wangjiong on 2017/12/18.
*/
public class CompoundView extends LinearLayout {
private ProgressBar pbar;
private Button btnCancel;
private int max = 100;// 最大進度(這裏默認爲100,自己可以隨便設置,看業務)
private int progress = 0;// 當前進度(這裏默認爲0,自己可以隨便設置,看業務)
/**
* 設置最大值
*
* @param max 最大值
*/
public void setMax(int max) {
this.max = max;
pbar.setMax(max);
}
/**
* 設置當前進度
*
* @param progress 進度
*/
public void setProgress(int progress) {
if (progress > max) {
this.progress = max;
} else {
this.progress = progress;
}
pbar.setProgress(this.progress);
}
public void setBtnCancelListener(OnClickListener lickListener) {
btnCancel.setOnClickListener(lickListener);// 給取消按鈕設置監聽(這裏也可以用接口回調供外部類調用)
}
/**
* 子控件均被映射成XML文件才觸發的(在xml靜態設置屬性之後,動態設置屬性之前)
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
pbar.setMax(max);
pbar.setProgress(progress);
}
public CompoundView(Context context) {
super(context);
// 初始化佈局
initView(context);
}
public CompoundView(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();
}
/**
* 初始化佈局
*/
private void initView(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 爲自定義屬性
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
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">
<Button
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"/>
<!--命名空間的前綴是可以自定義的,只要保證上下一致即可。這裏我用了wj-->
<com.wj.test.view.CompoundView
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"/>
</android.support.constraint.ConstraintLayout>
-
外部類的代碼中驗證。如果同時在xml裏和代碼裏設置屬性,那麼代碼的設置會覆蓋掉xml的,常識哦
/**
* 外部類測試自定義控件
* Created by wj on 2017/12/19 17:05
*/
public class CustomWidgetActivity extends AppCompatActivity {
private CompoundView view;// 自定義控件對象
private boolean isNotFinish = true;// 是否結束
private int i;
@Override
protected void onCreate(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(new View.OnClickListener() {// 取消監聽
@Override
public void onClick(View v) {
isNotFinish = false;
i = 0;
view.setProgress(i);
}
});
Button down = (Button) findViewById(R.id.btn_down);
down.setOnClickListener(new View.OnClickListener() {// 下載監聽
@Override
public void onClick(final View v) {
isNotFinish = true;
// 子線程模擬進度
new Thread() {
@Override
public void run() {
while (isNotFinish && i <= 100) {
try {
Thread.sleep(1000);
// 主線程更新UI
runOnUiThread(new Runnable() {
@Override
public void run() {
view.setProgress(i++);
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
});
}
}
全劇終
源碼地址:https://github.com/GodJiong/MyApplication