自定义View之BottomTitleImageView

效果图

image-20240225160633838

View生命周期

Alt text

  1. 主要由三部分操作完成,分别是measure,layout,draw:
  • measure:计算视图大小
  • layouty:设置视图在屏幕中显示的位置
  • draw:绘制视图.其中onDraw()方法会花费大量时间,布局变化会重绘视图,所以在onDraw()中要避免对象分配.
  1. invalidate() 和requsetLaytout()作用如下:
  • invalidate():重新绘制view,执行draw()操作。
  • requsetLaytout():重新请求绘制view,执行measure()和layout()过程,但不执行draw()操作。

定义和加载自定义属性

  1. 在values文件夹中定义属性文件attrs_wcircle_view.xml。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <resources>
    <declare-styleable name="WBottomTitleView">
    <attr name="textString" format="string" />
    <attr name="textDimension" format="dimension" />
    <attr name="textColor" format="color" />
    <attr name="mAlpha" format="integer" />
    <attr name="mTextBgColor" format="color" />
    <attr name="textDrawable" format="color|reference" />
    </declare-styleable>
    </resources>
  2. 在自定义布局的构造方法中加载自定义属性,根据属性更新画笔。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    public WBottomTitleView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init(attrs, defStyle);
    }

    private void init(AttributeSet attrs, int defStyle) {
    //加载 attributes
    final TypedArray typedArray = getContext().obtainStyledAttributes(
    attrs, R.styleable.WBottomTitleView, defStyle, 0);

    mTextString = typedArray.getString(
    R.styleable.WBottomTitleView_textString);
    mTextColor = typedArray.getColor(
    R.styleable.WBottomTitleView_textColor,
    mTextColor);
    mTextDimension = typedArray.getDimension(
    R.styleable.WBottomTitleView_textDimension,
    mTextDimension);
    mAlpha = typedArray.getInt(R.styleable.WBottomTitleView_mAlpha, mAlpha);
    mTextBgColor = typedArray.getInt(R.styleable.WBottomTitleView_mTextBgColor, mTextBgColor);
    if (typedArray.hasValue(R.styleable.WBottomTitleView_textDrawable)) {
    mTextDrawable = typedArray.getDrawable(
    R.styleable.WBottomTitleView_textDrawable);
    mTextDrawable.setCallback(this);
    }
    typedArray.recycle();
    mTextPaint = new TextPaint();
    mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
    mTextPaint.setTextAlign(Paint.Align.LEFT);
    p = new Paint();
    invalidateTextPaintAndMeasurements();
    }

    //根据attributes更新TextPaint
    private void invalidateTextPaintAndMeasurements() {
    mTextPaint.setTextSize(mTextDimension);
    mTextPaint.setColor(mTextColor);
    if (TextUtils.isEmpty(mTextString)) {
    mTextString = "";
    }
    Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
    mTextHeight = fontMetrics.bottom;
    }
  • TypedArray是一个数组容器,用于存储加载的属性。记住:用完必须recycle(),不然会发生内存泄漏。
  • Paint.FontMetrics解析:Alt text
    1) top是一行文字的上边界
    2)ascent是文字可视区域的上边界
    3)descent是文字可视区域的下边界
    4)bottom是一行文字的下边界
    5)leading是行与行之间的间距(通常为0,bottom与descent及top与ascent之间的间距足够间隔行行)

从上图中可以发现文字的可视区域在ascent与descent之间,top与bottom见的距离是整个文字所占空间的高度。

onDraw()绘制视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();
int contentWidth = getWidth() - paddingLeft - paddingRight;
int contentHeight = getHeight() - paddingTop - paddingBottom;
mTextPaintfontMetrics = mTextPaint.getFontMetrics();
p.setColor(mTextBgColor);// 设置灰色
p.setAlpha(mAlpha);
p.setStyle(Paint.Style.FILL);//设置填满
canvas.drawRect(paddingLeft, contentHeight - (mTextPaintfontMetrics.bottom - mTextPaintfontMetrics.top), contentWidth, contentHeight, p);// 矩形
// Draw the text.
canvas.drawText(mTextString,
paddingLeft,
paddingTop + (contentHeight - mTextHeight),
mTextPaint);
// Draw the text drawable on top of the text.
if (mTextDrawable != null) {
mTextDrawable.setBounds(paddingLeft, paddingTop,
paddingLeft + contentWidth, paddingTop + contentHeight);
mTextDrawable.draw(canvas);
}
}

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package cn.studyou.myviewdeep.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.widget.ImageView;

import cn.studyou.myviewdeep.R;


/**
* 基本功能:带有底部标题的ImageView
* 创建:王杰
* 创建时间:2017-02-18
*/

public class WBottomTitleView extends ImageView {
private String mTextString;
private int mTextColor = Color.RED;
private int mAlpha = 150;
private float mTextDimension = 0;
private Drawable mTextDrawable;
private Paint p;
private TextPaint mTextPaint;
private float mTextHeight;
private int mTextBgColor = Color.DKGRAY;
private Paint.FontMetrics mTextPaintfontMetrics;

public WBottomTitleView(Context context) {
super(context);
init(null, 0);
}

public WBottomTitleView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs, 0);
}

public WBottomTitleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs, defStyle);
}

private void init(AttributeSet attrs, int defStyle) {
//加载 attributes
final TypedArray typedArray = getContext().obtainStyledAttributes(
attrs, R.styleable.WBottomTitleView, defStyle, 0);

mTextString = typedArray.getString(
R.styleable.WBottomTitleView_textString);
mTextColor = typedArray.getColor(
R.styleable.WBottomTitleView_textColor,
mTextColor);
mTextDimension = typedArray.getDimension(
R.styleable.WBottomTitleView_textDimension,
mTextDimension);
mAlpha = typedArray.getInt(R.styleable.WBottomTitleView_mAlpha, mAlpha);
mTextBgColor = typedArray.getInt(R.styleable.WBottomTitleView_mTextBgColor, mTextBgColor);
if (typedArray.hasValue(R.styleable.WBottomTitleView_textDrawable)) {
mTextDrawable = typedArray.getDrawable(
R.styleable.WBottomTitleView_textDrawable);
mTextDrawable.setCallback(this);
}
typedArray.recycle();
mTextPaint = new TextPaint();
mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setTextAlign(Paint.Align.LEFT);
p = new Paint();
invalidateTextPaintAndMeasurements();
}

//根据attributes更新TextPaint
private void invalidateTextPaintAndMeasurements() {
mTextPaint.setTextSize(mTextDimension);
mTextPaint.setColor(mTextColor);
if (TextUtils.isEmpty(mTextString)) {
mTextString = "";
}
Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
mTextHeight = fontMetrics.bottom;
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();
int contentWidth = getWidth() - paddingLeft - paddingRight;
int contentHeight = getHeight() - paddingTop - paddingBottom;
mTextPaintfontMetrics = mTextPaint.getFontMetrics();
p.setColor(mTextBgColor);// 设置灰色
p.setAlpha(mAlpha);
p.setStyle(Paint.Style.FILL);//设置填满
canvas.drawRect(paddingLeft, contentHeight - (mTextPaintfontMetrics.bottom - mTextPaintfontMetrics.top), contentWidth, contentHeight, p);// 矩形

// Draw the text.
canvas.drawText(mTextString,
paddingLeft,
paddingTop + (contentHeight - mTextHeight),
mTextPaint);

// Draw the text drawable on top of the text.
if (mTextDrawable != null) {
mTextDrawable.setBounds(paddingLeft, paddingTop,
paddingLeft + contentWidth, paddingTop + contentHeight);
mTextDrawable.draw(canvas);
}

}

public String gettextString() {
return mTextString;
}

public void setTextString(String textString) {
mTextString = textString;
invalidateTextPaintAndMeasurements();
}

public int getTextColor() {
return mTextColor;
}

public void setTextColor(int textColor) {
mTextColor = textColor;
invalidateTextPaintAndMeasurements();
}

public float getTextDimension() {
return mTextDimension;
}

public void setTextDimension(float textDimension) {
mTextDimension = textDimension;
invalidateTextPaintAndMeasurements();
}

public Drawable getTextDrawable() {
return mTextDrawable;
}

public void setTextDrawable(Drawable textDrawable) {
mTextDrawable = textDrawable;
}

public int getmTextBgColor() {
return mTextBgColor;
}

public void setmTextBgColor(int mTextBgColor) {
this.mTextBgColor = mTextBgColor;
}

public int getmAlpha() {
return mAlpha;
}

public void setmAlpha(int mAlpha) {
this.mAlpha = mAlpha;
}
}

开始使用自定义View

  1. 在布局文件中引入View
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorAccent"
    >

    <cn.studyou.myviewdeep.view.WBottomTitleView
    android:layout_width="match_parent"
    android:layout_height="170dp"
    android:id="@+id/wCircleView"
    android:scaleType="fitXY"
    app:textDimension="18sp"
    app:textColor="#ffffff"
    />

    </RelativeLayout>
  2. Activity中设置图片和标题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package cn.studyou.myviewdeep;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.bumptech.glide.Glide;

import butterknife.BindView;
import butterknife.ButterKnife;
import cn.studyou.myviewdeep.view.WBottomTitleView;

public class MainActivity extends AppCompatActivity {

@BindView(R.id.wCircleView)
WBottomTitleView wCircleView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
wCircleView.setTextString(" 今天天气不错,感觉挺好的。 ");
Glide.with(this).load("http://img06.tooopen.com/images/20170214/tooopen_sy_198645593736.jpg").into(wCircleView);
}
}