?? : 本篇‘由簡入奢’,全是干貨!
- 繪制文字與BaseLine思考
- 文字居中
- 裁剪
- 從左到右漸變文字
- 從右到左漸變文字
- 最終實作效果(漸變滑動)
- 過度繪制極限優化
先來看看完成的效果:
簡單解釋: 在滑動的程序中,漸變文字會隨著ViewPager的滑動而變化!!
繪制文字與BaseLine思考
先來看看最初版代碼:
public class GradualChangeTv extends AppCompatTextView {
public Paint mPaint = new Paint();
public final String text = "android 超級兵";
public GradualChangeTv(Context context) {
this(context, null);
}
public GradualChangeTv(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GradualChangeTv(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint.setColor(Color.RED);
//抗鋸齒
mPaint.setAntiAlias(true);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/*
* 繪制文字
* 引數一: 繪制文字
* 引數二: x軸開始位置
* 引數三: y 軸開始位置
* 引數四: 畫筆
*/
canvas.drawText(text, 0, 0, mPaint);
}
}
就是簡單的繪制了一行字
疑問:
-
為什么這里要繼承自AppCompatTextView 而不是View
答: 偷個懶而已,因為不用在我來測量View,直接用父類的就行
來看看效果順便也看看布局:
出現問題: 文字并沒有顯示
答:因為文字坐標系和螢屏坐標系不一樣,文字坐標系是從BaseLine線開始計算的
先來回顧一下螢屏的坐標系
在來看看文字的坐標系
(圖片來自于網路)
再來思考一下文字是為什么不顯示的:
- 虛線為BaseLine
如果此時我把字體放大到100,看一看我能不能看到文字
再一次證明了文字是從BaseLine線開始繪制
文字居中
可以用兩條輔助線,水平線與垂直線.然后在來看文字是否居中
代碼:
?? : 底部會給出完整代碼.這里看思路即可,不用復制代碼
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//獲取當前控制元件的寬高
int viewWidth = getWidth() / 2;
int viewHeight = getHeight() / 2;
/*
* 繪制文字
* 引數一: 繪制文字
* 引數二: x軸開始位置
* 引數三: y 軸開始位置
* 引數四: 畫筆
*/
canvas.drawText(text, viewWidth, viewHeight, mPaint);
//繪制居中線
drawCenterLine(canvas, viewWidth, viewHeight);
}
private void drawCenterLine(Canvas canvas, int viewWidth, int viewHeight) {
//垂直線
canvas.drawLine(viewWidth, 0, viewWidth, getHeight(), mPaint);
//水平線
canvas.drawLine(0,viewHeight,getWidth(),viewHeight,mPaint);
}
效果圖:
可以看出,還是上面說的那個問題,文字繪制是基于baseLine線來繪制的.
文字居中思路:
- 通過mPaint.measureText(text) 獲取文字寬
- 通過mPaint.descent() + mPaint.ascent(); 獲取文字高
- 然后控制元件各取一半,讓控制元件減去即可
這里的descent和ascent可以參考上面文字繪制圖
相關代碼:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//文字寬度
float textWidth = mPaint.measureText(text);
//文字高度
float textHeight = mPaint.descent() + mPaint.ascent();
//獲取當前控制元件的寬高
int viewWidth = getWidth() / 2;
int viewHeight = getHeight() / 2;
canvas.drawText(text, viewWidth - textWidth / 2, viewHeight - textHeight / 2, mPaint);
//繪制居中線
drawCenterLine(canvas, viewWidth, viewHeight);
}
效果圖:
裁剪
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//文字寬度
float textWidth = mPaint.measureText(text);
//文字高度
float textHeight = mPaint.descent() + mPaint.ascent();
//獲取當前控制元件的寬高的一半
int viewWidth = getWidth() / 2;
int viewHeight = getHeight() / 2;
//裁剪
drawClip(canvas, viewWidth, viewHeight, textWidth, textHeight);
//繪制居中線
drawCenterLine(canvas, viewWidth, viewHeight);
}
private void drawClip(Canvas canvas, int viewWidth, int viewHeight, float textWidth, float textHeight) {
mPaint.setColor(Color.BLACK);
canvas.save();
//繪制文字X軸的位置
float left = viewWidth - textWidth / 2;
//繪制文字Y軸的位置
float right = viewHeight - textHeight / 2;
//裁剪
canvas.clipRect((int) left, 0, (int) left + 300, getHeight());
/*
* 繪制文字
* 引數一: 繪制文字
* 引數二: x軸開始位置
* 引數三: y 軸開始位置
* 引數四: 畫筆
*/
canvas.drawText(text, left, right, mPaint);
canvas.restore();
}
裁剪(clipRect)引數分析:
- 引數一: 從文字開始位置繪制
- 引數二: 頂部裁剪為0
- 引數三: 裁剪寬度
- 引數四: 繪制高度
canvas.save(),和canvas.restore();方法
可以理解為:將當前繪制的東西當作一個新的圖層!
來看看效果圖:

代碼注釋很清晰;就不過多解釋了
從左到右漸變文字
眾所周知.在android中,是不能夠將文字繪制一般的
思路分析:
- 繪制兩層(兩層顏色不同),兩層疊加起來
- 然后通過裁剪將上面一層給裁剪掉
在來看看現在代碼是什么樣子的:
//用來記錄當前進度 【0-1】
float progress = 0.3f;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//文字寬度
float textWidth = mPaint.measureText(text);
//文字高度
float textHeight = mPaint.descent() + mPaint.ascent();
//獲取當前控制元件的寬高的一半
int viewWidth = getWidth() / 2;
int viewHeight = getHeight() / 2;
//繪制底層
drawBottom(canvas, viewWidth, viewHeight, textWidth, textHeight);
//繪制上層【顏色漸變的】
drawUp(canvas, viewWidth, viewHeight, textWidth, textHeight);
//繪制居中線
drawCenterLine(canvas, viewWidth, viewHeight);
}
//繪制上層【漸變的】
private void drawUp(Canvas canvas, int viewWidth, int viewHeight, float textWidth, float textHeight) {
mPaint.setColor(Color.BLACK);
canvas.save();
//繪制文字X軸的位置
float left = viewWidth - textWidth / 2;
//繪制文字Y軸的位置
float right = viewHeight - textHeight / 2;
//裁剪
canvas.clipRect((int) left, 0, (int) left + textWidth * progress, getHeight());
/*
* 繪制文字
* 引數一: 繪制文字
* 引數二: x軸開始位置
* 引數三: y 軸開始位置
* 引數四: 畫筆
*/
canvas.drawText(text, left, right, mPaint);
canvas.restore();
}
//繪制下層 不動的
private void drawBottom(Canvas canvas, int viewWidth, int viewHeight, float textWidth, float textHeight) {
mPaint.setColor(Color.RED); //文字顏色
canvas.save();
//文字開始位置
float left = viewWidth - textWidth / 2;
/*
* 繪制文字
* 引數一: 繪制文字
* 引數二: x軸開始位置
* 引數三: y 軸開始位置
* 引數四: 畫筆
*/
canvas.drawText(text, left, viewHeight - textHeight / 2, mPaint);
canvas.restore();
}
這里重點解釋一下上層[需要裁剪的]引數:
//裁剪
canvas.clipRect((int) left, 0, (int) left + textWidth * progress, getHeight());
- textWidth 需要繪制文字的寬度
- viewWidth 控制元件寬度的一半
- 文字開始的位置:left = viewWidth - textWidth / 2;
- 文字需要裁剪的位置: 文字的寬度 * progress
通過手勢滑動來控制:
這段代碼并沒有實質性作用,只是來看看效果:
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE) {
progress = event.getX() / getWidth();
invalidate();
}
return true;
}
效果圖:
從右到左漸變文字
思路和從左到右繪制是一樣的直接看關鍵代碼:
private void drawRightToLeft(Canvas canvas, int viewWidth, int viewHeight, float textWidth, float textHeight) {
mPaint.setColor(Color.GREEN);
/*
* 這里 left和right能夠在此抽取出來,不過這樣寫很易懂,有需求自己弄吧!!!
*/
canvas.save();
//繪制文字X軸的位置 【文字開始的位置】
float textX = viewWidth - textWidth / 2;
//繪制文字Y軸的位置
float textY = viewHeight - textHeight / 2;
//文字結束的位置
float end = viewWidth + mPaint.measureText(text) / 2;
canvas.clipRect(end, 0, textX + textWidth * (1 - progress), getHeight());
canvas.drawText(text, textX, textY, mPaint);
canvas.restore();
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (type == GradualChangeTextView.GRADUAL_CHANGE_RIGHT) {
//從右到左滑動
progress = 1 - event.getX() / getWidth();
} else if (type == GradualChangeTextView.GRADUAL_CHANGE_LEFT) {
//從左到右滑動
progress = event.getX() / getWidth();
}
invalidate();
}
return true;
}
效果圖:
最后在添加兩個按鈕來完全測驗一下代碼有沒有問題:
完完全全沒有問題!
最終實作效果(漸變滑動)
先來看看布局:

布局簡單的很,就是文字和ViewPager
大致看看ViewPager代碼:
//text1 .. text4 是控制元件id
val textList = listOf(text1, text2, text3, text4)
val list = listOf(HomeFragment(), MyFragment(), TestFragment(), SettingFragment())
val viewPagerAdapter = ViewPagerAdapter(supportFragmentManager, list)
viewPager.adapter = viewPagerAdapter
//默認選擇第一頁
viewPager.currentItem = 1
//默認選中
textList[viewPager.currentItem].percent = 1f
這段代碼,只要學過就懂,不細說了!
重中之重來了:
viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int,
) {
if (positionOffset > 0) {
val left = textList[position]
val right = textList[position + 1]
//從右到左滑動
left.setSlidingPosition(GradualChangeTextView.GRADUAL_CHANGE_RIGHT)
//從左到右滑動
right.setSlidingPosition(GradualChangeTextView.GRADUAL_CHANGE_LEFT)
//當前頁面取反[從右到左]
left.percent = 1 - positionOffset
//下一個頁面正常[從左到右]
right.percent = positionOffset
}
}
override fun onPageSelected(position: Int) { }
override fun onPageScrollStateChanged(state: Int) {
//當 ViewPage結束的時候,重新設定一下狀態 [不設定的話滑動太快,會導致'殘影']
textList.forEach {
if (it.tag == textList[viewPager.currentItem].tag) {
it.percent = 1f
} else {
it.percent = 0f
}
}
}
})
來看看效果:
過度繪制極限優化
什么是過度繪制:
參考檔案
重點總結:
- 1.原色 – 沒有被過度繪制 – 這部分的像素點只在螢屏上繪制了一次,
- 2.藍色 – 1次過度繪制– 這部分的像素點只在螢屏上繪制了兩次,
- 3.綠色 – 2次過度繪制 – 這部分的像素點只在螢屏上繪制了三次,
- 4.粉色 – 3次過度繪制 – 這部分的像素點只在螢屏上繪制了四次,
- 5.紅色 – 4次過度繪制 – 這部分的像素點只在螢屏上繪制了五次,
先來看看沒有優化的效果:
可以看到,在繪制的程序中,因為是兩層,那么就繪制了2次,
優化思路:
當黑色[上層]從左到右滑動的時候,紅色[下層]跟隨著從左到右裁剪
來看看下層繪制的代碼:
//繪制下層 不動的
private void drawBottom(Canvas canvas, int viewWidth, int viewHeight, float textWidth, float textHeight) {
mPaint.setColor(Color.RED);
canvas.save();
//繪制文字X軸的位置 [文字開始的位置]
float textX = viewWidth - textWidth / 2;
//繪制文字Y軸的位置
float textY = viewHeight - textHeight / 2;
//跟隨者上層裁剪
canvas.clipRect((int) textX + textWidth * progress, 0, textWidth + viewWidth, getHeight());
/*
* 繪制文字
* 引數一: 繪制文字
* 引數二: x軸開始位置
* 引數三: y 軸開始位置
* 引數四: 畫筆
*/
canvas.drawText(text, textX, textY, mPaint);
canvas.restore();
}
效果圖:
完整代碼
原創不易,您的點贊就是對我最大的支持!
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/293390.html
標籤:其他
上一篇:騰訊社招Android 崗面經分享,僥幸斬獲Offer
下一篇:Android 語音提示
