自定義控件我有跟着幾個大神做的成果做一些,但是一直沒有仔細的完整的學一遍理論,是以這章要好好學一下。
這一章的内容如下:
——Android控件架構
——View的測量與繪制
——ViewGroup的測量與繪制
——自定義控件的三種方式
——事件的攔截機制
一、Android控件架構
如下圖展示了View視圖樹,在控制樹的頂部是一個ViewParent對象,是整個樹的控制核心,所有互動管理事件都由它來統一排程和配置設定。ViewGroup控件作為父控件可以包含多個View控件,并管理其包含的View控件。通常在Activity中使用的findViewById()方法,就是在控制樹中以樹的深度優先周遊來查找對應元素。
每個Activity都包含一個Window對象,Android中通常用PhoneWindow來實作。PhoneWindow将一個DecorView設定為整個應用視窗的根View,它封裝了一些視窗操作的通用方法,在顯示上,它将螢幕分為兩部分:TitleView和ContentView。如下圖:
setContentView()即加載ContentView部分的布局。如果使用者通過設定requestWindowFeature(Window.FEATURE_NO_TITLE)來設定全屏顯示,就一定要放在setContentView()方法之前才能生效。
在代碼中,當程式在onCreate()方法中調用setContentView()方法後,ActivityManagerService會回調onResume()方法,此時系統才會把整個DecorView添加到PhoneWindow中,并讓其顯示出來,進而最終完成界面繪制。
二、View的測量
Android系統提供了一個強大的類——MeasureSpec類來幫助我們測量View。它是一個32位的int值,高2位為測量的模式,低30位為測量的大小。測量的模式可以為以下三種
EXACTLY——精确值模式:當控件的寬高屬性指定為具體數值時使用
AT_MOST——最大值模式:當控件的寬高屬性指定為自适應時使用
UNSPECIFIED——不精确模式:它不指定其大小測量模式,通常在繪制自定義View時才使用。
在自定義控件時,若指定了控件具體寬高值或者是match_parent屬性,則使用EXACTLY模式,使用指定的specSize即可;如果指定的是wrap_content屬性,即AT_MOST模式,必須重寫onMeasure()方法來指定大小,通常作法是取出我們指定的大小與specSize中最小的一個來作為最後的測量值。代碼如下:
public class MainActivity extends View {
public MainActivity(Context context) {
super(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));
}
private int measureHeight(int heightMeasureSpec) {
int result = ;
int specMode = MeasureSpec.getMode(heightMeasureSpec);
int specSize = MeasureSpec.getSize(heightMeasureSpec);
if (specMode == MeasureSpec.EXACTLY){
result = specSize;
}else{
result = ;
if(specMode == MeasureSpec.AT_MOST){
result = Math.min(result,result);
}
}
return result;
}
private int measureWidth(int widthMeasureSpec) {
int result = ;
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
if (specMode == MeasureSpec.EXACTLY){
result = specSize;
}else{
result = ;
if(specMode == MeasureSpec.AT_MOST){
result = Math.min(result,result);
}
}
return result;
}
}
三、View的繪制
測量好一個View後,通常需要通過繼承View并重寫它的onDraw()方法來完成繪圖。onDraw()中有一個參數,就是Canvas對象,這個對象就像建立了一個畫闆,使用Paint就可以在上面作畫了。建立Cavas對象代碼:
Canvas canvas = new Canvas(bitmap);
傳進去的bitmap與Canvas畫布緊緊聯系,這個過程稱為裝載畫布,這個bitmap用來存儲繪制在Canvas上的像素資訊,調用所有的Canvas.drawXXX方法都發生在這個bitmap上。
canvas.drawBitmap(bitmap1,0,0,null);
//繪制直線
canvas.drawLine(float startX, float startY, float stopX, float stopY, Paint paint);
//繪制矩形
canvas.drawRect(float left, float top, float right, float bottom, Paint paint);
//繪制圓形
canvas.drawCircle(float cx, float cy, float radius, Paint paint);
//繪制字元
canvas.drawText(String text, float x, float y, Paint paint);
//繪制圖形
canvas.drawBirmap(Bitmap bitmap, float left, float top, Paint paint);
四、ViewGroup的測量與繪制
ViewGroup會管理其子View,其中一個管理項目就是負責子View的顯示大小。當ViewGroup指定為wrap_content時,ViewGroup會對子View周遊,從子View的大小來确定自己的大小。如果ViewGroup有指定的值則按值設定自己的大小。
子View測量完後,ViewGroup會周遊子View的Layout方法來指定其具體顯示的位置,進而決定其布局位置。
自定義ViewGroup時,通常會重寫onLayout()方法來控制其子View顯示位置的邏輯。
ViewGroup通常不需繪制。但是它會使用dispatchDraw()方法來繪制其子View,其過程是通過周遊是以子View,并調用子View的繪制方法來完成繪制工作。
五、自定義View
心心念念想學的一部分。。它的作用就不用多說了,直接快點開始學把~
在View中比較重要的回調方法
1、onFinishInflate()
//從XML加載元件後回調
@Override
protected void onFinishInflate() {
// TODO Auto-generated method stub
super.onFinishInflate();
}
2、onSizeChanged()
//元件大小改變時回調
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// TODO Auto-generated method stub
super.onSizeChanged(w, h, oldw, oldh);
}
3、onMeasure()
// 回調該方法進行測量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
4、onLayout()
// 回調該方法來确定顯示的位置
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
// TODO Auto-generated method stub
super.onLayout(changed, left, top, right, bottom);
}
5、onTouchEvent()
// 監聽到觸摸時間時回調
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
return super.onTouchEvent(event);
}
6、onDraw()
// 繪圖
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
}
建立自定義View的時候,并不需要重寫所有的方法。通常有一下三種方法來實作自定義的控件:
對現有控件進行拓展
通過組合來實作新的控件
重寫View來實作全新的控件
1、對現有控件進行拓展
這種方法就是在原生控件的基礎上進行拓展,增加新的功能、修改顯示的UI等。
第一個例子:讓TextView的背景更加豐富
TextView使用onDraw()方法繪制要顯示的文字,繼承了系統的TextView後,一定要重寫其onDraw()方法才能修改TextView展示的 效果。其實程式調用super.onDraw()方法來實作原生控件的功能,但是在調用super.onDraw()方法前和之後,我們可以實作自己的邏輯,即在系統繪制文字前後,完成自己的操作。如下所示:
@Override
protected void onDraw(Canvas canvas) {
//在回調父類之前,實作自己的邏輯,對textview來說就是繪制文本内容前
super.onDraw(canvas);
//在回調父類之後,實作自己的邏輯,對textview來說就是繪制文本内容後
}
然後我們分析要實作這樣的效果,需要繪制兩個不同大小的矩形,形成一個重疊效果。那我們就要先初始化兩個畫筆:
//執行個體化畫筆
paint1 = new Paint();
//設定顔色
paint1.setColor(getResources().getColor(android.R.color.holo_blue_light));
//設定style
paint1.setStyle(Paint.Style.FILL);
//執行個體化另一個畫筆
paint2 = new Paint();
paint2.setColor(Color.YELLOW);
paint2.setStyle(Paint.Style.FILL);
然後開始繪制
//繪制外層
canvas.drawRect(, , getMeasuredWidth(), getMeasuredHeight(), paint1);
//繪制内層
canvas.drawRect(, , getMeasuredWidth() - , getMeasuredHeight() - , paint2);
canvas.save();
//繪制文字前平移10像素
canvas.translate(, );
//父類完成方法
super.onDraw(canvas);
canvas.restore();
第二個例子:實作一個動态的文字閃動效果
想要實作這一效果,可以充分利用Android中Paint對象的Shader渲染器。通過設定一個不斷變化的LinearGradient,并使用帶有該屬性的Paint對象來繪制要顯示的文字。首先我們要在onSizeChanged()方法中完成一些初始化操作。
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mViewWidth == ) {
mViewWidth = getMeasuredWidth();
if (mViewWidth > ) {
//擷取畫筆對象
mPaint = getPaint();
//渲染器
mLinearGradient = new LinearGradient(, , mViewWidth, , new int[]{Color.BLUE, , Color.BLUE},
null, Shader.TileMode.CLAMP);
mPaint.setShader(mLinearGradient);
//矩陣
matrix = new Matrix();
}
}
}
其中最關鍵的就是使用getPaint()方法擷取目前繪制TextView的Paint對象,并給這個Paint對象設定原聲TextView沒有的LinearGradient屬性。最後在onDraw()方法中,通過矩陣的方法來不斷平移漸變效果,進而産生動态閃動效果:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (matrix != null) {
mTranslate += mViewWidth + ;
if (mTranslate > * mViewWidth / ) {
mTranslate = -mViewWidth;
}
matrix.setTranslate(mTranslate, );
mLinearGradient.setLocalMatrix(matrix);
//每隔100毫秒閃動一下
postInvalidateDelayed();
}
}
}
下面放上完整的效果和代碼:
布局代碼
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.administrator.myview.TextViewTestOne
android:id="@+id/tv_01"
android:layout_width="match_parent"
android:layout_height="80dp"
android:text="Android"
android:textSize="25sp"/>
<com.example.administrator.myview.TextViewTestTwo
android:id="@+id/tv_02"
android:layout_marginTop="30dp"
android:layout_width="match_parent"
android:layout_height="80dp"
android:text="Android"
android:textSize="25sp"/>
TextViewTestOne.java
public class TextViewTestOne extends TextView {
private Paint paint1, paint2;
public TextViewTestOne(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
//執行個體化畫筆1
paint1 = new Paint();
//設定顔色
paint1.setColor(getResources().getColor(android.R.color.holo_blue_light));
//設定style
paint1.setStyle(Paint.Style.FILL);
//同上
paint2 = new Paint();
paint2.setColor(Color.YELLOW);
paint2.setStyle(Paint.Style.FILL);
}
@Override
protected void onDraw(Canvas canvas) {
//繪制外層
canvas.drawRect(, , getMeasuredWidth(), getMeasuredHeight(), paint1);
//繪制内層
canvas.drawRect(, , getMeasuredWidth() - , getMeasuredHeight() - , paint2);
canvas.save();
//繪制文字前平移10像素
canvas.translate(, );
//父類完成方法
super.onDraw(canvas);
canvas.restore();
}
}
TextViewTestTwo.java
public class TextViewTestTwo extends TextView {
private LinearGradient mLinearGradient;
private Matrix mGradientMatrix;
private Paint mPaint;
int mTranslate;
int mViewWidth;
public TextViewTestTwo(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if(mViewWidth == ){
mViewWidth = getMeasuredWidth();
if(mViewWidth>){
mPaint = getPaint();
mLinearGradient = new LinearGradient(,,mViewWidth,,
new int[]{
Color.BLUE,,Color.BLUE},null,Shader.TileMode.CLAMP
);
mPaint.setShader(mLinearGradient);
mGradientMatrix = new Matrix();
}
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(mGradientMatrix!=null){
mTranslate += mViewWidth/;
if(mTranslate>*mViewWidth){
mTranslate = -mViewWidth;
}
mGradientMatrix.setTranslate(mTranslate,);
mLinearGradient.setLocalMatrix(mGradientMatrix);
postInvalidateDelayed();
}
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextViewTestOne tv_01 = (TextViewTestOne) findViewById(R.id.tv_01);
TextViewTestTwo tv_02 = (TextViewTestTwo) findViewById(R.id.tv_02);
}
}
2、建立複合控件
這種方式通常需要繼承一個合适的ViewGroup,再給它添加指定功能的控件,進而組合成新的複合控件。通過這種方式建立的控件,我們一般會給他指定一些可配置的屬性,使其具有更強的拓展性。以下是一個TopBar示例。
1)定義屬性
在res資源目錄下建立一個attrs.xml的屬性定義檔案,然後通過如下代碼定義相應的屬性即可。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="TopBar">
<attr name="title" format="string" />
<attr name="titleTextSize" format="dimension" />
<attr name="titleTextColor" format="color" />
<attr name="leftTextColor" format="color" />
<attr name="leftBackground" format="reference|color" />
<attr name="leftText" format="string" />
<attr name="rightTextColor" format="color" />
<attr name="rightBackground" format="reference|color" />
<attr name="rightText" format="string" />
</declare-styleable>
</resources>
确定号屬性後,就可以建立一個自定義控件TopBar,并讓他繼承ViewGroup,為了簡單這裡繼承了RelativeLayout。在構造方法中通過如下代碼來擷取在xml布局檔案中自定義的屬性:
系統提供了TypedArray這樣的資料機構來擷取自定義屬性集,通過它的對象的getString(),getColor()等方法,就可以擷取這些定義的屬性值,代碼如下:
//通過這個方法,你可以從你的attrs.xml檔案下讀取讀取到的值存儲在你的TypedArray
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TopBar);
//讀取出相應的值設定屬性
mLeftTextColor = ta.getColor(R.styleable.TopBar_leftTextColor, );
mLeftBackground = ta.getDrawable(R.styleable.TopBar_leftBackground);
mLeftText = ta.getString(R.styleable.TopBar_leftText);
mRightTextColor = ta.getColor(R.styleable.TopBar_rightTextColor, );
mRightBackgroup = ta.getDrawable(R.styleable.TopBar_rightBackground);
mRightText = ta.getString(R.styleable.TopBar_rightText);
mTitleSize = ta.getDimension(R.styleable.TopBar_titleTextSize, );
mTitleColor = ta.getColor(R.styleable.TopBar_titleTextColor, );
mTitle = ta.getString(R.styleable.TopBar_title);
//擷取完TypedArray的值之後,一般要調用recycle方法來避免重複建立時候的錯誤
ta.recycle();
2)組合控件
接下來,就要開始組合基礎控件。建立左邊的按鈕,中間的标題欄和右邊的按鈕,設定他們的一些屬性,然後設定各個控件在ViewGroup中的布局,最後用addView()方法将這三個控件加入到定義的TopBar模闆中。
mLeftButton = new Button(context);
mRightButton = new Button(context);
mTitleView = new TextView(context);
//為建立的元素指派
mLeftButton.setTextColor(mLeftTextColor);
mLeftButton.setBackground(mLeftBackground);
mLeftButton.setText(mLeftText);
mRightButton.setTextColor(mRightTextColor);
mRightButton.setBackground(mRightBackgroup);
mRightButton.setText(mRightText);
mTitleView.setText(mTitle);
mTitleView.setTextColor(mTitleColor);
mTitleView.setTextSize(mTitleSize);
mTitleView.setGravity(Gravity.CENTER);
//為元件元素設定相應的布局元素
mLeftParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT);
mLeftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT,TRUE);
//添加到ViewGroup
addView(mLeftButton,mLeftParams);
mRightParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT);
mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,TRUE);
addView(mRightButton,mRightParams);
mTitleParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT);
mTitleParams.addRule(RelativeLayout.CENTER_IN_PARENT,TRUE);
addView(mTitleView,mTitleParams);
定義接口——接下來建立一個接口,實作按鈕的點選回調
//定義接口
//接口對象,實作回調機制,在回調方法中
//通過映射的接口對象調用接口中的方法
//而不用去考慮如何實作,具體的實作由調用者去建立
public interface topbarClickListener {
//左按鈕點選事件
void leftClick();
//右按鈕點選事件
void rightClick();
}
暴露接口給調用者——在模闆方法中,為左右按鈕增加點選事件
//按鈕的點選事件,不需要具體的實作
//隻需調用接口的方法,回調的時候,會有具體的實作
mLeftButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
mListener.leftClick();
}
});
mRightButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
mListener.rightClick();
}
});
//暴露一個方法給調用者來注冊接口回調
//通過接口來獲得回調者對接口方法的實作
public void setOnTopbarClickListener(topbarClickListener mListener){
this.mListener = mListener;
}
實作接口回調——在調用這的代碼中,要實作這樣一個接口,并完成接口中的方法
mTopbar = (TopBar) findViewById(R.id.mTopbar);
mTopbar.setOnTopbarClickListener(new topbarClickListener() {
@Override
public void leftClick() {
Toast.makeText(MainActivity.this, "點選左側按鈕", Toast.LENGTH_SHORT).show();
}
@Override
public void rightClick() {
Toast.makeText(MainActivity.this, "點選右側按鈕", Toast.LENGTH_SHORT).show();
}
});
為了簡單,隻顯示了兩個Toast來區分不同按鈕的點選事件
3)引用UI模闆
最後一步,自然是在需要使用的地方引用UI模闆,在引用前,需要指定引用第三方控件的名字空間。
例如
這行代碼就是在指定引用的名字空間xmlns,即xml namespace。這裡指定了名字空間為“android”,是以在接下來使用系統屬性時,才可以使用“android:”來引用Android的系統屬性。
如果使用自定義屬性,就要建立自己的名字空間:
<?xml version="1.0" encoding="utf-8"?>
<com.example.administrator.topbartest.TopBar
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="40dp"
custom:leftBackground="#128"
custom:leftText="Back"
custom:leftTextColor="#FFFFFF"
custom:rightBackground="#128"
custom:rightText="More"
custom:rightTextColor="#FFFFFF"
custom:title="自定義标題"
custom:titleTextColor="#000"
custom:titleTextSize="15sp">
</com.example.administrator.topbartest.TopBar>
通過如上所示代碼,我們就可以在其他的布局中,直接通過标簽來引用這個UI模闆View。
下面放上完整的代碼
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--聲明使用自定義屬性,通過name屬性來确定引用的名字-->
<declare-styleable name="TopBar">
<attr name="title" format="string" />
<attr name="titleTextSize" format="dimension" />
<attr name="titleTextColor" format="color" />
<attr name="leftTextColor" format="color" />
<attr name="leftBackground" format="reference|color" />
<attr name="leftText" format="string" />
<attr name="rightTextColor" format="color" />
<attr name="rightBackground" format="reference|color" />
<attr name="rightText" format="string" />
</declare-styleable>
</resources>
topbarClickListener.java
//定義接口
//接口對象,實作回調機制,在回調方法中
//通過映射的接口對象調用接口中的方法
//而不用去考慮如何實作,具體的實作由調用者去建立
public interface topbarClickListener {
//左按鈕點選事件
void leftClick();
//右按鈕點選事件
void rightClick();
}
TopBar.java
public class TopBar extends RelativeLayout {
private int mLeftTextColor;
private Drawable mLeftBackground;
private String mLeftText;
private int mRightTextColor;
private Drawable mRightBackgroup;
private String mRightText;
private float mTitleSize;
private int mTitleColor;
private String mTitle;
private Button mLeftButton,mRightButton;
private TextView mTitleView;
private LayoutParams mLeftParams,mRightParams,mTitleParams;
private topbarClickListener mListener;
//帶參構造方法
public TopBar(Context context, AttributeSet attrs) {
super(context, attrs);
//通過這個方法,你可以從你的attrs.xml檔案下讀取讀取到的值存儲在你的TypedArray
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TopBar);
//讀取出相應的值設定屬性
mLeftTextColor = ta.getColor(R.styleable.TopBar_leftTextColor, );
mLeftBackground = ta.getDrawable(R.styleable.TopBar_leftBackground);
mLeftText = ta.getString(R.styleable.TopBar_leftText);
mRightTextColor = ta.getColor(R.styleable.TopBar_rightTextColor, );
mRightBackgroup = ta.getDrawable(R.styleable.TopBar_rightBackground);
mRightText = ta.getString(R.styleable.TopBar_rightText);
mTitleSize = ta.getDimension(R.styleable.TopBar_titleTextSize, );
mTitleColor = ta.getColor(R.styleable.TopBar_titleTextColor, );
mTitle = ta.getString(R.styleable.TopBar_title);
//擷取完TypedArray的值之後,一般要調用recycle方法來避免重複建立時候的錯誤
ta.recycle();
mLeftButton = new Button(context);
//按鈕的點選事件,不需要具體的實作
//隻需調用接口的方法,回調的時候,會有具體的實作
mLeftButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
mListener.leftClick();
}
});
mRightButton = new Button(context);
mRightButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
mListener.rightClick();
}
});
mTitleView = new TextView(context);
//為建立的元素指派
mLeftButton.setTextColor(mLeftTextColor);
mLeftButton.setBackground(mLeftBackground);
mLeftButton.setText(mLeftText);
mRightButton.setTextColor(mRightTextColor);
mRightButton.setBackground(mRightBackgroup);
mRightButton.setText(mRightText);
mTitleView.setText(mTitle);
mTitleView.setTextColor(mTitleColor);
mTitleView.setTextSize(mTitleSize);
mTitleView.setGravity(Gravity.CENTER);
//為元件元素設定相應的布局元素
mLeftParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT);
mLeftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT,TRUE);
//添加到ViewGroup
addView(mLeftButton,mLeftParams);
mRightParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT);
mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,TRUE);
addView(mRightButton,mRightParams);
mTitleParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT);
mTitleParams.addRule(RelativeLayout.CENTER_IN_PARENT,TRUE);
addView(mTitleView,mTitleParams);
}
//暴露一個方法給調用者來注冊接口回調
//通過接口來獲得回調者對接口方法的實作
public void setOnTopbarClickListener(topbarClickListener mListener){
this.mListener = mListener;
}
}
topbar.xml
<?xml version="1.0" encoding="utf-8"?>
<com.example.administrator.topbartest.TopBar
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="40dp"
custom:leftBackground="#128"
custom:leftText="Back"
custom:leftTextColor="#FFFFFF"
custom:rightBackground="#128"
custom:rightText="More"
custom:rightTextColor="#FFFFFF"
custom:title="自定義标題"
custom:titleTextColor="#000"
custom:titleTextSize="15sp">
</com.example.administrator.topbartest.TopBar>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
layout="@layout/topbar"
android:id="@+id/mTopbar"/>
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
private TopBar mTopbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTopbar = (TopBar) findViewById(R.id.mTopbar);
mTopbar.setOnTopbarClickListener(new topbarClickListener() {
@Override
public void leftClick() {
Toast.makeText(MainActivity.this, "點選左側按鈕", Toast.LENGTH_SHORT).show();
}
@Override
public void rightClick() {
Toast.makeText(MainActivity.this, "點選右側按鈕", Toast.LENGTH_SHORT).show();
}
});
}
}
3、重寫View來實作全新的控件
例子一:比例圖
我們來分析一下這個圖是怎麼畫出來的。首先這個自定義View可以分為三部分,分别是中間的圓形,中間顯示的文字和外圈的弧線。
下面直接看完整代碼:
CircleView.java
public class CircleView extends View {
//圓的長度
private int mCircleXY;
//螢幕高寬
private int w,h;
//圓的半徑
private float mRadius;
//圓的畫筆
private Paint mCirclePaint;
//弧線的畫筆
private Paint mArcPaint;
//文本畫筆
private Paint mTextPaint;
//需要顯示的文字
private String mShowText = "hahahaha";
//文字大小
private int mTextSize = ;
//圓心掃描的弧度
private int mSweepAngle = ;
public CircleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//擷取螢幕高寬
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
w = wm.getDefaultDisplay().getWidth();
h = wm.getDefaultDisplay().getHeight();
init();
}
private void init() {
mCircleXY = w/;
mRadius = (float)(w * / );
mCirclePaint = new Paint();
mCirclePaint.setColor(Color.BLUE);
mArcPaint = new Paint();
//設定線寬
mArcPaint.setStrokeWidth();
//設定空心
mArcPaint.setStyle(Paint.Style.STROKE);
//設定顔色
mArcPaint.setColor(Color.BLUE);
mTextPaint = new Paint();
mTextPaint.setColor(Color.WHITE);
mTextPaint.setTextSize(mTextSize);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//繪制矩形
RectF mArcRectF = new RectF((float)(w * ),(float)(w * ),(float)(w * ),(float)(w * ));
//繪制圓
canvas.drawCircle(mCircleXY,mCircleXY,mRadius,mCirclePaint);
//繪制弧線
canvas.drawArc(mArcRectF,,mSweepAngle,false,mArcPaint);
//繪制文本
canvas.drawText(mShowText,,mShowText.length(),mCircleXY-mTextSize * ,mCircleXY+mTextSize/,mTextPaint);
}
//設定一個對外的弧度方法
public void setSweepValues(int sweepValues){
if(sweepValues != ){
mSweepAngle = sweepValues;
}else {
//如果沒有,我們預設設定
mSweepAngle = ;
}
//重新整理
invalidate();
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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">
<com.example.administrator.circledemo.CircleView
android:id="@+id/cv"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CircleView circleView = (CircleView) findViewById(R.id.cv);
circleView.setSweepValues(180);
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CircleView circleView = (CircleView) findViewById(R.id.cv);
circleView.setSweepValues();
}
}
例子二、音頻條形圖
直接看代碼吧
YinPinRectView.java
public class YinPinRectView extends View {
private Paint mPaint;
//定義矩形數量
int mRectCount = ;
//定義矩形寬度和高度
private int mRectWidth ,mRectHeight;
//設定偏移量
private int offset = ;
//定義目前矩形高度
private float currentHeight;
//設定一個随機數
private double mRandom;
private LinearGradient mLinearGradient;
//螢幕寬高
private int w;
public YinPinRectView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
w = wm.getDefaultDisplay().getWidth();
init();
}
private void init() {
mPaint = new Paint();
//設定線寬
mPaint.setStrokeWidth();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for(int i = ;i<mRectCount;i++){
currentHeight = getRectHeight();
canvas.drawRect((float)(w * + mRectWidth * i + offset),currentHeight,
(float)(w * + mRectWidth * (i+) ),mRectHeight,mPaint);
}
postInvalidateDelayed();
}
//随即改變矩形的高
private float getRectHeight() {
mRandom = Math.random();
float currentHeight = (float) (mRectHeight * mRandom);
return currentHeight;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
w = getWidth();
mRectHeight = getHeight();
mRectWidth = (int)(w /mRectCount);//計算矩形的寬
//漸變效果
mLinearGradient = new LinearGradient(,,mRectWidth,mRectCount,Color.YELLOW,Color.BLUE, Shader.TileMode.CLAMP);
mPaint.setShader(mLinearGradient);
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.administrator.yinpinrectdemo.YinPinRectView
android:id="@+id/yp"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
YinPinRectView yp = (YinPinRectView) findViewById(R.id.yp);
}
}
五、事件攔截機制分析
http://blog.csdn.net/chunqiuwei/article/details/41084921