自定義View
??在平時(shí)工作中,總會(huì)需要一些特別的需求,而這些需求是Android系統(tǒng)自帶控件不能實(shí)現(xiàn)的,所以我們就需要自定義View來實(shí)現(xiàn)業(yè)務(wù)需求。
- 繼承控件:主要完成的功能是在原來的控件基礎(chǔ)上添加一些新的功能
- 組合控件:將多個(gè)的單一的控件組合成一個(gè)控件。
- 完全自定義控件:編寫一個(gè)類直接繼承View,重寫里面的onMeasure()方法確定尺寸,重寫onLayout()方法確定布局位置,重寫onDraw()方法繪畫控件的形狀,再加上相應(yīng)的回調(diào)函數(shù)和自定義屬性來完成相應(yīng)的控件的展示和數(shù)據(jù)的回調(diào)。
所有的自定義View都是重寫的有兩個(gè)參數(shù)的構(gòu)造函數(shù),因?yàn)橄到y(tǒng)默認(rèn)就是調(diào)用的有兩個(gè)參數(shù)的構(gòu)造函數(shù)。
完全自定義View(控件)
- 自定義一個(gè)類繼承于View,并重寫里面包含兩個(gè)參數(shù)的構(gòu)造函數(shù)
- 重寫onMeasure()方法,用于測量自身的尺寸
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec)
widthMeasureSpec:
heightMeasureSpec:這兩個(gè)參數(shù)是從父容器得來的,這兩個(gè)參數(shù)分別實(shí)際上是兩個(gè)32位的2進(jìn)制數(shù)來表示的,這兩個(gè)32位的2進(jìn)制數(shù)的高兩位是當(dāng)前控件在xml中聲明的測量模式,后30位是父容器根據(jù)當(dāng)前的測量模式計(jì)算出來的建議的寬和高。(這個(gè)寬和高只是建議的,不是實(shí)際的)
/**
* 測量自身的寬高尺寸
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//下面設(shè)置默認(rèn)的寬和高
int describWidth = 200;
int describHeight = 200;
//獲取寬高的測量模式
int widthModel = MeasureSpec.getMode(widthMeasureSpec);
int heightModel = MeasureSpec.getMode(heightMeasureSpec);
//獲取父類建議的寬高的值
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//通過測量模式確定寬高的最終值
int width = 0;
int height = 0;
if (widthModel == MeasureSpec.AT_MOST) { //說明使用的是那個(gè)warp_content模式
width = Math.min(describWidth,widthSize);
}else if (widthModel == MeasureSpec.EXACTLY){ //說明使用的是具體的數(shù)值或者match_parent這種測量模式
width = widthSize;
}
if (heightModel == MeasureSpec.AT_MOST){
height = Math.min(describHeight,heightSize);
}else if (heightModel == MeasureSpec.EXACTLY){
height = heightSize;
}
setMeasuredDimension(width,height);
}
測量模式:
- MeasureSpec.AT_MOST:--->對(duì)應(yīng)咱們在xml布局文件中聲明的wrap_content
- MeasureSpec.EXACTLY:--->對(duì)應(yīng)布局文件中的match_parent/實(shí)際的數(shù)值
默認(rèn)情況下,1和2是相等的,也就是自定義View默認(rèn)情況下是屏幕大小- MeasureSpec.UNSPECIFIED:--->指的是控件的寬高不受父控件的影響,想有多高就有多高,一般用于系統(tǒng)組件,咱們用不著
- 重寫onDraw()方法繪制控件的形狀
protected void onDraw(Canvas canvas) //用來畫控件的長相的
mPaint.setAntiAlias(true); //設(shè)置抗鋸齒,為了讓邊緣平滑
/**
* 繪制控件的形狀
*
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//獲取畫筆的對(duì)象
Paint paint = new Paint();
paint.setColor(getResources().getColor(R.color.colorAccent));
paint.setStrokeWidth(1);//設(shè)置線條(畫筆)的高度
paint.setAntiAlias(true); //設(shè)置抗鋸齒,邊緣平滑
//獲取控件測量出來的寬高
//這個(gè)寬高指的是控件沒有超出屏幕情況下的寬和高 如果超出了屏幕,那么這個(gè)值就是屏幕的寬高
//getWidth:獲取的是控件的寬度
//getHeight:獲取的是控件的高度
//只有超出了屏幕的情況下,下面這個(gè)才能獲取真正的寬和高。如果沒有超出屏幕,那么兩種方法獲取到的值是相等的。
getMeasuredHeight();
getMeasuredWidth();
//畫出自定義View的整體輪廓和形狀
canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
}
- 如果需要添加事件的話就需要回調(diào)
自定義屬性
- 在Values目錄下面建立一個(gè)attr的xml文件
- 在里面添加屬性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="diyTextView">
<attr name="diyText" format="string" />
<attr name="diyColor" format="string" />
</declare-styleable>
<declare-styleable name="diyEditView">
<attr name="diyEditColor" format="color" />
<attr name="diyPadding" format="integer" />
</declare-styleable>
</resources>
- 在需要使用自定義屬性的地方引入
xmlns:diyTextView ="http://schemas.android.com/apk/res-auto"
引入的時(shí)候,名字需要統(tǒng)一
diyTextView:diyText = "自定義屬性"
- 在自定義View中獲取屬性
- 第一種方式:
int count = attrs.getAttributeCount();
for (int i=0;i<count;i++){
String attrName=attrs.getAttributeName(i);
String attrValue=attrs.getAttributeValue(i);
Log.e("---名稱:"+attrName,"---值:"+attrValue);
}
- 第二個(gè)方式:
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.bobo);
String text=array.getString(R.styleable.bobo_myText);
String color=array.getString(R.styleable.bobo_myColor);
/* Log.e("------------",text);
Log.e("------------",color);*/
array.recycle(); //表示循環(huán)的取出消息
盡量使用第二種方式,因?yàn)榈谝环N方式在使用了資源引用的時(shí)候,獲取的值是資源R文件里面的引用而不是值。
這兩種獲取屬性方式的卻別是什么?
第一種方式獲取屬性值的時(shí)候,如果存在資源的引用的話沒有辦法直接獲取屬性值。第二種方式相當(dāng)于第一種方式的封裝,能夠直接通過屬性的名稱來獲取屬性的值。
刷新當(dāng)前控件的方法:
invalidate();//刷新控件不能控件的寬和高
requestLayout();//刷新控件可以改變控件的寬高
繼承控件
??一般情況下也即是繼承一個(gè)已知控件然后完成相應(yīng)的多功能操作。
實(shí)例:仿照筆記本樣式
/**
* 仿照筆記本
*/
public class MyEditText extends EditText{
int color=Color.BLACK; //這個(gè)呢是這個(gè)顏色
int padding=50; //這個(gè)呢是那個(gè)邊距
public MyEditText(Context context, AttributeSet attrs) {
super(context, attrs);
setGravity(Gravity.LEFT | Gravity.TOP ); //這里的|相當(dāng)于是與的關(guān)系,將EditText設(shè)置為從左上角開始
setPadding(60,0,60,0);//設(shè)置左上右下的內(nèi)邊距
setBackgroundResource(R.drawable.background );
}
/**
* 重新布局自己的EditText
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint mPaint=new Paint();
mPaint.setColor(color); //設(shè)置那個(gè)畫筆的顏色
mPaint.setAntiAlias(true); //設(shè)置那個(gè)抗鋸齒
mPaint.setStrokeWidth(1); //設(shè)置那個(gè)線條的寬度
//開始劃線
//從哪里劃線 到哪里結(jié)束?
int viewHeight=getHeight();
int viewWidth=getWidth();
//下一步應(yīng)該獲取一共應(yīng)該畫多少線
int lineCount=viewHeight/getLineHeight(); //計(jì)算出了一共需要畫出多少線
for (int i=0;i<lineCount-1;i++){
canvas.drawLine(60,(i+1)*getLineHeight(),viewWidth-60,(i+1)*getLineHeight(),mPaint);//參數(shù)分別是:起點(diǎn)x,起點(diǎn)y,終點(diǎn)x,終點(diǎn)y,畫筆
}
}
在需要使用該控件的地方聲明
<com.qf.mobilephone.android1606_28.view2.MyEditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="請輸入內(nèi)容"
/>
組合控件
??將多個(gè)已知的控件組合形成一個(gè)新的控件
案例:將ListView的一個(gè)item組合成一個(gè)
- 編寫一個(gè)View繼承于一個(gè)布局
- 在初始化這個(gè)控件的方法里面獲取布局加載器
- 編寫需要組合的控件的模板-->用xml文件描述
- 通過布局加載器加載布局文件
//首先獲取那個(gè)布局的加載器
mLayoutInflater= (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//將xml文件加載給當(dāng)前對(duì)象
View view = mLayoutInflater.inflate(R.layout.view3_layout, this);
- 找id,編寫方法
- 在使用了該控件的地方實(shí)現(xiàn)該控件中的回調(diào)接口,并重寫里面點(diǎn)擊事件。