Android群英傳讀書筆記---自定義控制元件(-)
自定義控制元件
雖然我也寫過自定義控制元件,但是從沒有進行一個系統的總結,正好借這本書的內容,重新梳理一下,
通常情況下,常用的有三種方法:
- 對現有控制元件進行擴充套件
- 通過組合控制元件來實現新的控制元件
- 重寫view來實現全新的控制元件(最難)
1. 對現有控制元件進行拓展
對原生控制元件的擴充套件,只需要重寫onDraw(Canvas canvas)即可
private void initPaint(){
paint1 = new Paint();
paint1.setColor(getResources().getColor(android.R .color.holo_blue_bright));
paint1.setStyle(Paint.Style.FILL);
paint2 = new Paint();
paint2.setColor(getResources().getColor(android.R.color.holo_green_dark));
paint2.setStyle(Paint.Style.FILL);
}
@Override
protected void onDraw(Canvas canvas) {
//TODO 回撥父類方法前,對TextView來說繪製文字內容之前
//繪製一個外層矩形
canvas.drawRect (0, 0, getMeasuredWidth(), getMeasuredHeight(), paint1);
//繪製一個內層矩形
canvas.drawRect(10, 10, getMeasuredWidth()-10, getMeasuredHeight()-10, paint2);
canvas.save();
//繪製文字前平移10px
canvas.translate(10, 0);
super.onDraw(canvas);
canvas.restore();
//TODO 回撥父類方法後,對TextView來說繪製文字內容之後
}
控制元件效果如下圖:
再來一個複雜點的效果(動態文字閃爍效果):
思路:通過設定一個不斷變化的LinearGradient,並使用帶有該屬性的Paint物件來繪製要顯示的文字。首先,在onSizeChanged(),中進行一些物件的初始化工作,根據view的寬設定一個LinearGradient漸變渲染器。
private int mViewWidth;
private Paint paint;
private LinearGradient linearGradient;
private Matrix matrix;
private int mTranslate;
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if(mViewWidth==0){
mViewWidth = getMeasuredWidth();
if(mViewWidth>0){
paint = getPaint();
linearGradient = new LinearGradient(0, 0, mViewWidth, 0,
new int[]{Color.BLUE,0xffffffff,Color.GREEN}, new float[]{0,1,2}, Shader.TileMode.MIRROR);
paint.setShader(linearGradient);
matrix = new Matrix();
}
}
}
其中最關鍵的是,使用getPaint()方法獲取繪製當前TextView的Paint,並給這個Paint設定一個LinearGradient,
最後,
在onDraw()方法中,通過矩陣的方式來不斷平移漸變效果
@Override
protected void onDraw(Canvas canvas) {
//TODO 回撥父類方法前,對TextView來說繪製文字內容之前
super.onDraw(canvas);
Log.e("mess", "------onDraw----");
if(matrix != null){
mTranslate += mViewWidth/5;
if(mTranslate>2*mViewWidth){
mTranslate =-mViewWidth;
}
matrix.setTranslate(mTranslate, 0);
linearGradient.setLocalMatrix(matrix);
postInvalidateDelayed(100);
}
//TODO 回撥父類方法後,對TextView來說繪製文字內容之後
}
下面看效果圖:
2. 建立複合控制元件
複合控制元件,最常見的其實就是我們的TitleBar了,一般就是一個left+title+right組合,我以前是做成一個xml檔案,然後各個需要的地方,用<\include>標籤引用,看了書上的例子,是把我這種xml的形式,做成了一個單獨控制元件,就學習學習這種寫法吧。
Begin
(1). values 目錄下新建attrs.xml 定義屬性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="TitleBar">
<!-- 定義title文字,大小,顏色 -->
<attr name="title" format="string" />
<attr name="titleTextSize" format="dimension"/>
<attr name="titleTextColor" format="color" />
<!-- 定義left 文字,大小,顏色,背景 -->
<attr name="leftText" format="string" />
<attr name="leftTextSize" format="dimension" />
<attr name="leftTextColor" format="color" />
<!-- 表示背景可以是顏色,也可以是引用 -->
<attr name="leftBackGround" format="color|reference" />
<!-- 定義right 文字,大小,顏色,背景 -->
<attr name="rightText" format="string" />
<attr name="rightTextSize" format="dimension"/>
<attr name="rightTextColor" format="color" />
<attr name="rightBackGround" format="color|reference" />
</declare-styleable>
</resources>
(2). 獲取自定義的屬性
/**
* 獲取自定義的屬性
*
* @param context
*/
private int leftTextColor;
private Drawable leftBackGround;
private String leftText;
private float leftTextSize;
private int rightTextColor;
private String rightText;
private float rightTextSize;
private int titleTextColor;
private String titleText;
private float titleTextSize;
private void initAttr(Context context, AttributeSet attrs) {
TypedArray typed = context.obtainStyledAttributes(attrs, R.styleable.TitleBar);
// 從TypedArray中取出對應的值為要設定的屬性賦值,給個預設值
leftTextColor = typed.getColor(R.styleable.TitleBar_leftTextColor, 0XFFFFFFFF);
leftBackGround = typed.getDrawable(R.styleable.TitleBar_leftBackGround);
leftText = typed.getString(R.styleable.TitleBar_leftText);
leftTextSize = typed.getDimension(R.styleable.TitleBar_leftTextSize, 20);
rightTextColor = typed.getColor(R.styleable.TitleBar_rightTextColor, 0XFFFFFFFF);
rightText = typed.getString(R.styleable.TitleBar_rightText);
rightTextSize = typed.getDimension(R.styleable.TitleBar_rightTextSize, 20);
titleTextColor = typed.getColor(R.styleable.TitleBar_titleTextColor, 0XFFFFFFFF);
titleText = typed.getString(R.styleable.TitleBar_title);
titleTextSize = typed.getDimension(R.styleable.TitleBar_titleTextSize, 20);
// 不要忘記呼叫
typed.recycle();
}
(3).程式碼佈局元件
private TextView titleView;
private Button leftButton;
private Button rightButton;
private RelativeLayout.LayoutParams leftParams;
private RelativeLayout.LayoutParams rightParams;
private RelativeLayout.LayoutParams titleParams;
/**
* 程式碼佈局
*
* @param context
*/
@SuppressWarnings("deprecation")
private void initView(Context context) {
titleView = new TextView(context);
leftButton = new Button(context);
rightButton = new Button(context);
// 為建立的元件賦值
titleView.setText(titleText);
titleView.setTextSize(titleTextSize);
titleView.setTextColor(titleTextColor);
titleView.setGravity(Gravity.CENTER);
leftButton.setText(leftText);
leftButton.setTextColor(leftTextColor);
leftButton.setBackgroundDrawable(leftBackGround);
leftButton.setTextSize(leftTextSize);
rightButton.setText(rightText);
rightButton.setTextSize(rightTextSize);
rightButton.setTextColor(rightTextColor);
// 為元件佈局
// 在左邊
leftParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
leftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
addView(leftButton, leftParams);
// 在右邊
rightParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
rightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
addView(rightButton, rightParams);
//中間
titleParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
rightParams.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
addView(titleView, titleParams);
//新增點選監聽,(下面講述如何引入的)
leftButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.leftClick();
}
}
});
rightButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.rightClick();
}
}
});
}
(4).定義介面
public interface TitleBarClickListener{
//左點選
void leftClick();
//右點選
void rightClick();
}
(5).暴露介面給呼叫者
public void setTitleBarClickListener(TitleBarClickListener listener) {
this.listener = listener;
}
看下例子 佈局檔案
<com.example.day_1.TitleBar
android:id="@+id/titlebar"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_alignParentBottom="true"
app:leftBackGround="#ff000000"
app:leftText="left"
app:leftTextColor="#ffff6734"
app:leftTextSize="25dp"
app:rightText="right"
app:rightTextSize="25dp"
app:rightTextColor="#ff123456"
app:title="title"
app:titleTextColor="#ff654321"/>
程式碼
private TitleBar titlebar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
titlebar = (TitleBar) findViewById(R.id.titlebar);
titlebar.setTitleBarClickListener(this);
}
@Override
public void leftClick() {
Toast.makeText(this, "left---", Toast.LENGTH_LONG).show();
}
@Override
public void rightClick() {
Toast.makeText(this, "right---", Toast.LENGTH_LONG).show();
}
效果如下圖:
3.重寫view實現全新的控制元件
這個是我覺著三種自定義型別裡,最難得了,迄今為止沒有寫出過什麼出彩的控制元件,所以還是好好學習書上的內容吧,增加新技能。
———————————Begin——————————-
(1) .弧線展示圖
思路:這個view可以分為三個部分,中間的圓圈,中間顯示的文字,外圈的圓弧。只要有了這樣的思路,剩餘的就是在onDraw()方法中去繪製了。
- 測量控制元件大小,onMeasure()
private int mMeasureHeigth;// 控制元件高度
private int mMeasureWidth;// 控制元件寬度
// 圓形
private Paint mCirclePaint;
private float mCircleXY;//圓心座標
private float mRadius;//圓形半徑
// 圓弧
private Paint mArcPaint;
private RectF mArcRectF;//圓弧的外切矩形
private float mSweepAngle;//圓弧的角度
private float mSweepValue = 50;
// 文字
private Paint mTextPaint;
private String mShowText;//文字內容
private float mShowTextSize;//文字大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mMeasureWidth = MeasureSpec.getSize(widthMeasureSpec);//獲取控制元件寬度
mMeasureHeigth = MeasureSpec.getSize(heightMeasureSpec);//獲取控制元件高度
setMeasuredDimension(mMeasureWidth, mMeasureHeigth);
initView();
}
/**
*準備畫筆,
*/
private void initView() {
float length = Math.min(mMeasureWidth,mMeasureHeigth)
// 圓
mCircleXY = length / 2;// 確定圓心座標
mRadius = (float) (length * 0.5 / 2);// 確定半徑
mCirclePaint = new Paint();
mCirclePaint.setAntiAlias(true);// 去鋸齒
mCirclePaint.setColor(getResources().getColor(android.R.color.holo_green_dark));
// 弧線
// 矩形
mArcRectF = new RectF((float) (length * 0.1), (float) (length * 0.1), (float) (length * 0.9),
(float) (length * 0.9));
mSweepAngle = (mSweepValue / 100f) * 360f;
mArcPaint = new Paint();
mArcPaint.setColor(getResources().getColor(android.R.color.holo_blue_bright));
mArcPaint.setStrokeWidth((float) (length * 0.1));//圓弧寬度
mArcPaint.setStyle(Style.STROKE);//圓弧
// 文字
mShowText = setShowText();
mShowTextSize = setShowTextSize();
mTextPaint = new Paint();
mTextPaint.setTextSize(mShowTextSize);
mTextPaint.setTextAlign(Paint.Align.CENTER);
}
private float setShowTextSize() {
this.invalidate();
return 50;
}
private String setShowText() {
this.invalidate();
return "Android Skill";
}
public void forceInvalidate() {
this.invalidate();
}
public void setSweepValue(float sweepValue) {
if (sweepValue != 0) {
mSweepValue = sweepValue;
} else {
mSweepValue = 25;
}
this.invalidate();
}
- 繪製控制元件, onDraw()
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 繪製圓
canvas.drawCircle(mCircleXY, mCircleXY, mRadius, mCirclePaint);
// 繪製圓弧,逆時針繪製,角度跟
canvas.drawArc(mArcRectF, 90, mSweepAngle, false, mArcPaint);
// 繪製文字
canvas.drawText(mShowText, 0, mShowText.length(), mCircleXY, mCircleXY + mShowTextSize / 4, mTextPaint);
}
圓弧的繪製,開始位置如下圖所示角度,以順時針繪製圖形
- 效果如下圖
(2).音訊條形圖
思路:繪製n個小矩形,每個矩形有些偏移即可
準備工作
private int mWidth;//控制元件的寬度
private int mRectWidth;// 矩形的寬度
private int mRectHeight;// 矩形的高度
private Paint paint;
private int mRectCount;// 矩形的個數
private int offset = 5;// 偏移
private double mRandom;
private LinearGradient lg;// 漸變
private void initView() {
paint = new Paint();
paint.setColor(Color.GREEN);
paint.setStyle(Paint.Style.FILL);
mRectCount = 12;
}
//重寫onSizeChanged方法
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = getWidth();
mRectHeight = getHeight();
mRectWidth = (int) (mWidth * 0.6 / mRectCount);
lg = new LinearGradient(0, 0, mRectWidth, mRectHeight, Color.GREEN, Color.BLUE, TileMode.CLAMP);
paint.setShader(lg);
}
//重寫onDraw方法
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < mRectCount; i++) {
mRandom = Math.random();
float currentHeight = (int) (mRectHeight * mRandom);
canvas.drawRect((float) (mWidth * 0.4 / 2 + mRectWidth * i + offset * i), currentHeight,
(float) (mWidth * 0.4 / 2 + mRectWidth * (i + 1) + offset * i), mRectHeight, paint);
}
postInvalidateDelayed(1000);
}
view的繪製座標如下圖所示:
效果圖如下:
後記:
就像本書作者說的:無論多麼複雜的自定義view都是慢慢迭代起來的功能,不要被自定義view嚇到,總之,非常感謝作者,我對自定義view總算是有了一個完整的認識