一、自定義控件分類
1.1、原生控件拓展
修改原有控件我們隻需要建立一個類繼承該View(ViewGroup),再原有的邏輯上添加自己的實作即可。
a)文本框(TextView)預設是無法擷取焦點的,想讓它擷取焦點,我們可以通過自定義控件,并重寫isFocused來解決。
public class FocuseTextView extends TextView {
public FocuseTextView(Context context) {
this(context,null);
}
public FocuseTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
@ExportedProperty(category = "focus")
public boolean isFocused() {
//欺騙系統,誤以為textview得到了焦點
return true;
}
}
b)繪制一個帶矩形邊框的TextView
public class MyTetView extends TextView {
private Paint mPaint1;
private Paint mpaint2;
public MyTetView(Context context) {
this(context,null);
}
public MyTetView(Context context, AttributeSet attrs) {
super(context, attrs);
initPaint();
}
private void initPaint() {
mPaint1 = new Paint();
mPaint1.setStyle(Paint.Style.FILL);
mPaint1.setColor(getResources().getColor(android.R.color.holo_blue_light));
mpaint2 = new Paint();
mpaint2.setStyle(Paint.Style.FILL);
mpaint2.setColor(Color.YELLOW);
}
@Override
protected void onDraw(Canvas canvas) {
// 繪制外層矩形
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint1);
// 繪制内層矩形
canvas.drawRect(10, 10, getMeasuredWidth() - 10, getMeasuredHeight() - 10, mpaint2);
canvas.save();
super.onDraw(canvas);
canvas.restore();
}
}
1.2、自定義組合控件
(1) 組合原生控件
在自定義組合控件時,我們并不需要自己去繪制視圖上顯示的内容,而隻是用系統原生的控件組合即可。
a) 我們首先制定好需要組合的控件,用它來達到我們想要的效果:
<RelativeLayout
android:id="@+id/rl_viewgroup"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="5dp"
android:textSize="20sp"
android:text="我是标題"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="5dp"
android:layout_below="@id/tv_content"
android:textColor="@android:color/darker_gray"
android:text="我是未被選中的描述"/>
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"/>
</RelativeLayout>
b) 在values目錄下建立attrs.xml檔案,并定義好屬性:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name = "combinationView">
<attr name = "title" format = "string"/>
<attr name = "content" format = "string"/>
<attr name = "focusable" format = "boolean"/>
</declare-styleable>
</resources>
c) 建立自定義控件類繼承自ViewGroup,并實作帶attrs的構造函數,再使用TypeArray來擷取屬性:
public class TextViewCheckBox extends RelativeLayout {
private TextView mTvTitle,mTvContent;
private CheckBox mCbClick;
private String mTitle,mContentOn,mContentOff;
public TextViewCheckBox(Context context) {
this(context,null);
}
public TextViewCheckBox(Context context, AttributeSet attrs) {
super(context, attrs);
// 初始化布局和控件
View view = View.inflate(context, R.layout.ui_text_checkbox, this);
mTvTitle = (TextView) view.findViewById(R.id.tv_title);
mTvContent = (TextView) view.findViewById(R.id.tv_content);
mCbClick = (CheckBox) view.findViewById(R.id.cb_click);
// 将attrs.xml中定義的所有屬性的值存儲到TypeArray中
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.combinationView);
mTitle = array.getString(R.styleable.combinationView_title);
mContentOn = array.getString(R.styleable.combinationView_content_on);
mContentOff = array.getString(R.styleable.combinationView_content_off);
array.recycle();
// 初始化子控件描述和狀态
if(mTitle != null){
mTvTitle.setText(mTitle);
}
if(mContentOff != null){
mTvContent.setText(mContentOff);
}
}
}
d) 暴露方法給調用者來設定描述和狀态:
/**判斷是否被選中*/
public boolean isChecked(){
return mCbClick.isChecked();
}
/**設定選中的狀态*/
public void setChecked(boolean isChecked){
mCbClick.setChecked(isChecked);
if(isChecked){
mTvContent.setText(mContentOn);
}else{
mTvContent.setText(mContentOff);
}
}
e) 在布局中引用該控件,引入名稱空間,并設定自定義的屬性。
<cn.legend.review.TextViewCheckBox
android:id="@+id/tvc_textchecked"
android:layout_width="match_parent"
android:layout_height="wrap_content"
review:title="我是标題"
review:content_on = "控件被選中"
review:content_off = "控件沒有選中"/>
注意:在使用自定義控件時需要引入名稱空間:xmlns:review="http://schemas.android.com/apk/res/cn.legend.review"
如果想讓控件響應事件的話,則直接重寫事件即可,如果用到了wrap_content或match_parent則需要進行測量等操作。
1.3、自定義屬性種類
1. reference:參考某一資源ID。
(1)屬性定義:
<declare-styleable name = "名稱">
<attr name = "background" format = "reference" />
</declare-styleable>
(2)屬性使用:
<ImageView
android:layout_width = "42dip"
android:layout_height = "42dip"
android:background = "@drawable/圖檔ID"/>
2. color:顔色值
(1)屬性定義:
<declare-styleable name = "名稱">
<attr name = "textColor" format = "color" />
</declare-styleable>
(2)屬性使用:
<TextView
android:layout_width = "42dip"
android:layout_height = "42dip"
android:textColor = "#00FF00"/>
3. boolean:布爾值
1)屬性定義:
<declare-styleable name = "名稱">
<attr name = "focusable" format = "boolean" />
</declare-styleable>
(2)屬性使用:
<Button
android:layout_width = "42dip"
android:layout_height = "42dip"
android:focusable = "true"/>
4. dimension:尺寸值
(1)屬性定義:
<declare-styleable name = "名稱">
<attr name = "layout_width" format = "dimension" />
</declare-styleable>
(2)屬性使用:
<Button
android:layout_width = "42dip"
android:layout_height = "42dip"/>
5. float:浮點值
(1)屬性定義:
<declare-styleable name = "AlphaAnimation">
<attr name = "fromAlpha" format = "float" />
<attr name = "toAlpha" format = "float" />
</declare-styleable>
(2)屬性使用:
<alpha
android:fromAlpha = "1.0"
android:toAlpha = "0.7"/>
6. integer:整型值
(1)屬性定義:
<declare-styleable name = "AnimatedRotateDrawable">
<attr name = "visible" />
<attr name = "frameDuration" format="integer" />
<attr name = "framesCount" format="integer" />
<attr name = "pivotX" />
<attr name = "pivotY" />
<attr name = "drawable" />
</declare-styleable>
(2)屬性使用:
<animated-rotate
xmlns:android = "http://schemas.android.com/apk/res/android"
android:drawable = "@drawable/圖檔ID"
android:pivotX = "50%"
android:pivotY = "50%"
android:framesCount = "12"
android:frameDuration = "100"/>
7. string:字元串
(1)屬性定義:
<declare-styleable name = "MapView">
<attr name = "apiKey" format = "string" />
</declare-styleable>
(2)屬性使用:
<com.google.android.maps.MapView
android:layout_width = "fill_parent"
android:layout_height = "fill_parent"
android:apiKey = "0jOkQ80oD1JL9C6HAja99uGXCRiS2CGjKO_bc_g"/>
8. fraction:百分數
(1)屬性定義:
<declare-styleable name="RotateDrawable">
<attr name = "visible" />
<attr name = "fromDegrees" format = "float" />
<attr name = "toDegrees" format = "float" />
<attr name = "pivotX" format = "fraction" />
<attr name = "pivotY" format = "fraction" />
<attr name = "drawable" />
</declare-styleable>
(2)屬性使用:
<rotate
xmlns:android = "http://schemas.android.com/apk/res/android"
android:interpolator = "@anim/動畫ID"
android:fromDegrees = "0" android:toDegrees = "360"
android:pivotX = "200%" android:pivotY = "300%"
android:duration = "5000" android:repeatMode = "restart"
android:repeatCount = "infinite"/>
9. enum:枚舉值
(1)屬性定義:
<declare-styleable name="名稱">
<attr name="orientation">
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
</declare-styleable>
(2)屬性使用:
<LinearLayout
xmlns:android = "http://schemas.android.com/apk/res/android"
android:orientation = "vertical"
android:layout_width = "fill_parent"
android:layout_height = "fill_parent"/>
</LinearLayout>
10. 位或運算
(1)屬性定義:
<declare-styleable name="名稱">
<attr name="windowSoftInputMode">
<flag name = "stateUnspecified" value = "0" />
<flag name = "stateUnchanged" value = "1" />
<flag name = "stateHidden" value = "2" />
<flag name = "stateAlwaysHidden" value = "3" />
<flag name = "stateVisible" value = "4" />
<flag name = "stateAlwaysVisible" value = "5" />
<flag name = "adjustUnspecified" value = "0x00" />
<flag name = "adjustResize" value = "0x10" />
<flag name = "adjustPan" value = "0x20" />
<flag name = "adjustNothing" value = "0x30" />
</attr>
</declare-styleable>
(2)屬性使用:
<activity
android:name = ".StyleAndThemeActivity"
android:label = "@string/app_name"
android:windowSoftInputMode = "stateUnspecified | stateUnchanged | stateHidden">
<intent-filter>
<action android:name = "android.intent.action.MAIN" />
<category android:name = "android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
注意:屬性定義時可以指定多種類型值,使用“|”進行隔離多種類型。
(1)屬性定義:
<declare-styleable name = "名稱">
<attr name = "background" format = "reference|color" />
</declare-styleable>
(2)屬性使用:
<ImageView
android:layout_width = "42dip"
android:layout_height = "42dip"
android:background = "@drawable/圖檔ID|#00FF00"/>
1.4、自定義屬性用法
在我們自定義的View中有需要自定義的屬性,則需要在values下建立attrs.xml,在其中定義你的屬性。
a)在res/values檔案下定義一個attrs.xml檔案,代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ToolBar">
<attr name="buttonNum" format="integer"/>
<attr name="itemBackground" format="reference|color"/>
</declare-styleable>
</resources>
b)在布局xml中如下使用該屬性:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:toolbar="http://schemas.android.com/apk/res/cn.zzm.toolbar"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<cn.zzm.toolbar.ToolBar
android:id="@+id/gridview_toolbar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@drawable/control_bar"
android:gravity="center"
toolbar:buttonNum="5"
toolbar:itemBackground="@drawable/control_bar_item_bg" />
</RelativeLayout>
2、兩種擷取方式
我們可以在自定義View的構造函數中擷取屬性的值,有以下兩種方式:
第一種擷取方式:
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.ToolBar);
int buttonNum = array.getInt(R.styleable.ToolBar_buttonNum, 5);
int itemBg = array.getResourceId(R.styleable.ToolBar_itemBackground, -1);
array.recycle();
第二種擷取方式:
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.ToolBar);
int count = array.getIndexCount();
for (int i = 0; i < count; i++) {
int attr = array.getIndex(i);
switch (attr) {
case R.styleable.ToolBar_buttonNum:
int buttonNum = array.getInt(attr, 5);
break;
case R.styleable.ToolBar_itemBackground:
int itemBg = array.getResourceId(attr, -1);
break;
}
}
3、使用要點
屬性類型:string、integer、dimension、reference、color、enum。
下面我看下使用自定義屬性需要注意的一些地方,首先看下attrs.xml檔案前面幾種聲明方式都是一緻的,例如:
<attr name="buttonNum" format="integer"/>
隻有enum是不同的,用法舉例:
<attr name="testEnum">
<enum name="fill_parent" value="-1"/>
<enum name="wrap_content" value="-2"/>
</attr>
如果該屬性可同時傳兩種不同的屬性,則可以用“|”分割開即可。讓我們再看看布局xml中需要注意的事項,在自定義元件的構造函數中使用:
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.ToolBar);
獲得對屬性集的引用,然後就可以用“array”的各種方法來擷取相應的屬性值。這裡需要注意的是,如果使用的方法和擷取值的類型不對的話,則會傳回預設值。
是以,如果一個屬性是帶兩個及以上不用類型的屬性,需要做多次判斷,直到讀取完畢後才能判斷應該賦予何值。
1.5、自定義View示範
編寫簡單驗證碼程式:
a) 自定義View的屬性,首先在res/value/下建立attrs.xml,裡面定義我們的屬性和聲明我們的整個樣式
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="titleText" format="string" />
<attr name="titleTextColor" format="color" />
<attr name="titleTextSize" format="dimension" />
<declare-styleable name="CustomTitleView">
<attr name="titleText" />
<attr name="titleTextColor" />
<attr name="titleTextSize" />
</declare-styleable>
</resources>
b) 我們定義字型、字型顔色、字型大小等3個屬性,foemat是指該屬性的取值類型。
然後在布局檔案中聲明我們自定義的View,注意:自定義屬性需要引入名稱空間
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res/cn.legend.demo"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="cn.legend.demo.MainActivity" >
<cn.legend.demo.CustomTitleView
android:layout_width="200dp"
android:layout_height="100dp"
custom:titleText="3721"
custom:titleTextColor = "#ff0000"
custom:titleTextSize="40sp"/>
</RelativeLayout>
c) 由于自定義屬性,我們的View必須實作帶attr的構造方法:
public class CustomTitleView extends View {
private String mCustomText;
private int mCustomTextColor;
private int mCustomTextSize;
private Rect mRect;
private Paint mPaint;
public CustomTitleView(Context context) {
this(context, null);
}
public CustomTitleView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomTitleView);
mCustomText = array.getString(R.styleable.CustomTitleView_titleText);
mCustomTextColor = array.getColor(R.styleable.CustomTitleView_titleTextColor, Color.BLACK);
mCustomTextSize = (int) array.getDimension(R.styleable.CustomTitleView_titleTextSize, 16);
array.recycle();
initPaint();
}
private void initPaint() {
mPaint = new Paint();
mPaint.setTextSize(mCustomTextSize);
mPaint.setColor(mCustomTextColor);
// 文字的骨骼
mRect = new Rect();
mPaint.getTextBounds(mCustomText, 0, mCustomText.length(), mRect);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 畫背景
mPaint.setColor(Color.YELLOW);
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
mPaint.setColor(mCustomTextColor);
canvas.drawText(mCustomText, getWidth() / 2 - mRect.width() /2, getHeight() / 2 + mRect.height() / 2, mPaint);
}
}
運作效果:
d) 當我們将布局檔案中的寬和高為明确的值時,系統測量的結果就是我們設定的結果,如果wrap_content或match_parent時,
系統預設會讓寬和高和窗體同寬高,因為系統根本不知道我們的控件的具體大小,我們可以通過重寫onMeasure方法
重寫之前先了解MeasureSpec的specMode有三種類型:
- EXACTLY:一般是設定了明确的值或者是MATCH_PARENT
- AT_MOST:表示子布局限制在一個最大值内,一般為WARP_CONTENT
- UNSPECIFIED:表示子布局想要多大就多大,很少使用
e) 測量是比較複雜的,我們來測量下該控件:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width,height;
if(widthMode == MeasureSpec.EXACTLY){// 明确的值或match_parent
width = widthSize;
}else{
mPaint.setTextSize(mCustomTextSize);
mPaint.getTextBounds(mCustomText, 0, mCustomText.length(), mRect);
int textWidth = mRect.width();
// 期望的值,左右邊距加上文本的寬度
width = getPaddingLeft() + textWidth + getPaddingRight();
}
if(heightMode == MeasureSpec.EXACTLY){
height = heightSize;
}else{
mPaint.setTextSize(mCustomTextSize);
mPaint.getTextBounds(mCustomText, 0, mCustomText.length(), mRect);
int textHeight = mRect.height();
// 期望的值,上下邊距加上文本的高度
height = getPaddingTop() + textHeight + getPaddingBottom();
}
setMeasuredDimension(width, height);
}
由于我們讓寬度和高度分别是左邊距 + 右邊距 + 文本寬度 和 上邊距 + 下邊距 + 文本高度,是以我們布局必須加入padding值:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res/cn.legend.demo"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="cn.legend.demo.MainActivity" >
<cn.legend.demo.CustomTitleView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
custom:titleText="3721"
android:padding="10dp"
custom:titleTextColor = "#ff0000"
custom:titleTextSize="40sp"/>
</RelativeLayout>
e) 接下來給該控件加入事件
this.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 生成四位随機數
Random random = new Random();
Set<Integer> set = new HashSet<Integer>();
while(set.size() < 4){
int randomInt = random.nextInt(10);
set.add(randomInt);
}
StringBuffer sb = new StringBuffer();
for (Integer integer : set) {
sb.append("" + integer);
}
mCustomText = sb.toString();
// 重繪
postInvalidate();
}
});
##############################################待續
轉載于:https://www.cnblogs.com/pengjingya/p/5510217.html