Android 自定义View(二)——字体渐变
目录
示例图
1、创建自定义属性
values创建attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ColorTrackTextView">
<attr name="originColor" format="color"/>
<attr name="changeColor" format="color"/>
</declare-styleable>
</resources>
2、创建自定义VIew
public class ColorTrackTextView extends AppCompatTextView {
// 绘制不变色字体的画笔
private Paint mOriginPaint;
// 绘制变色字体的画笔
private Paint mChangePaint;
// 当前变色的进度
private float mCurrentProgress = 0.5f;
// 实现不同朝向
private Direction mDirection;
public enum Direction {
LEFT_TO_RIGHT, RIGHT_TO_LEFT
}
public ColorTrackTextView(@NonNull Context context) {
this(context, null);
}
public ColorTrackTextView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ColorTrackTextView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaint(context, attrs);
}
private void initPaint(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ColorTrackTextView);
int originColor = typedArray.getColor(R.styleable.ColorTrackTextView_originColor, getTextColors().getDefaultColor());
int changeColor = typedArray.getColor(R.styleable.ColorTrackTextView_changeColor, getTextColors().getDefaultColor());
// 回收
typedArray.recycle();
// 不变色的画笔
mOriginPaint = getPaintByColor(originColor);
// 变色的画笔
mChangePaint = getPaintByColor(changeColor);
}
/**
* 根据颜色获取画笔
*/
private Paint getPaintByColor(int color) {
Paint paint = new Paint();
// 设置颜色
paint.setColor(color);
// 设置抗锯齿
paint.setAntiAlias(true);
// 防抖动
paint.setDither(true);
// 设置字体的大小
paint.setTextSize(getTextSize());
return paint;
}
@Override
protected void onDraw(Canvas canvas) {
int currentPoint = (int) (mCurrentProgress * getWidth());
// 从左边到右边变色
if (mDirection == Direction.LEFT_TO_RIGHT) {
// 绘制变色的部分 -- 开始 currentPoint = 0;结束 currentPoint = getWidth
drawText(canvas, mChangePaint, 0, currentPoint);
// 绘制不变色的部分
drawText(canvas, mOriginPaint, currentPoint, getWidth());
} else {
// 绘制变色的部分 -- 开始 currentPoint = getWidth;结束 currentPoint = 0
drawText(canvas, mChangePaint, getWidth() - currentPoint, getWidth());
// 绘制不变色的部分
drawText(canvas, mOriginPaint, 0, getWidth() - currentPoint);
}
}
private void drawText(Canvas canvas, Paint paint, int start, int end) {
// 此时要保存这个图层,进行操作
canvas.save();
Rect rect = new Rect(start, 0, end, getHeight());
// 用于裁剪绘图区域
canvas.clipRect(rect);
String text = getText().toString();
// 判空
if (TextUtils.isEmpty(text)) return;
// 获取文字的区域
Rect bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds);
// 获取x坐标
int dx = getWidth() / 2 - bounds.width() / 2;
// 获取基线 baseLine
Paint.FontMetricsInt fontMetricsInt = mChangePaint.getFontMetricsInt();
int dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
int baseLine = getHeight() / 2 + dy;
// 绘制文字
canvas.drawText(text, dx, baseLine, paint);
// 绘制为 未操作的图层。
canvas.restore();
}
public void setCurrentProgress(float currentProgress) {
this.mCurrentProgress = currentProgress;
invalidate();
}
public void setDirection(Direction direction) {
this.mDirection = direction;
}
}
注意:首先文字宽高是不能直接获取的,需要获取,这里首先调用getTextBounds()把文字框起来了,这样就有一个宽高的样子。计算文字宽高需要一个baseline
baseline图参数含义
- FontMetricInt.top:这是一个负数,顶部到baseline的距离,一般文字到达不了,为什么需要有这个呢,因为国际化的原因。
- FontMetricInt.ascent:从Baseline基线到字体最顶部的距离,是一个 负值。
- 它代表了文字顶部超出Baseline基线的部分。例如,对于字母 b、d,顶部超过基线的那一部分就是 ascent 描述的区域。
- FontMetricInt.descent:从Baseline基线到字体最底部的距离,是一个 正值。
- 它代表了文字下方延伸到Baseline基线以下的部分。例如,小写字母 "p" 和 "g" 的下半部分超过了基线,这部分就是 descent。
- FontMetricInt.bottom:这是一个正数,底部到baseline的距离,一般文字到达不了,为什么需要有这个呢,因为国际化的原因。
Paint.FontMetricsInt fontMetricsInt = mChangePaint.getFontMetricsInt();
int dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
// baselin的高度
int baseLine = getHeight() / 2 + dy;
根据上例代码,看图中的绿色字 baseline,这个需要求的。FontMetricInt.bottom 正数 - FontMetricInt.top 负数,就得到整个文字的整个高度。然后需要 /2 得到文字正中间,减去 fontMetricsInt.bottom,就得到了baseline那空隙的高度。然后和view的高度 /2 + dy空隙高度,就是baseLine高度。(如下图)
3、设置布局
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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">
<com.dfg.colortracktextview.ColorTrackTextView
android:id="@+id/color_track_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World"
android:textSize="20sp"
app:changeColor="#FF8A80"
app:originColor="#EA80FC"/>
<Button
android:id="@+id/left_to_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="从左往右"/>
<Button
android:id="@+id/right_to_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="从右往左"/>
</LinearLayout>
4、MainActivity
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.rightToLeft.setOnClickListener {
setAnimation(ColorTrackTextView.Direction.RIGHT_TO_LEFT);
}
binding.leftToRight.setOnClickListener {
setAnimation(ColorTrackTextView.Direction.LEFT_TO_RIGHT);
}
}
fun setAnimation(direction: ColorTrackTextView.Direction?) {
binding.colorTrackTv.setDirection(direction)
val valueAnimator = ObjectAnimator.ofFloat(0f, 1f)
valueAnimator.duration = 2000
valueAnimator.addUpdateListener { animation ->
val currentProgress = animation.animatedValue as Float
binding.colorTrackTv.setCurrentProgress(currentProgress)
}
valueAnimator.start()
}
}