Android 漸變的‘TabLayout’ , (含免費( java / kotlin) Demo)
本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家發佈
@TOC
先來看看完成的效果:
簡單解釋: 在滑動的過程中,漸變文字會隨着ViewPager的滑動而變化!!
繪製文字與BaseLine思考
先來看看最初版代碼:
```java 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線開始繪製
文字居中
可以用兩條輔助線,水平線與垂直線.然後在來看文字是否居中
代碼:
⚠️ : 底部會給出完整代碼.這裏看思路即可,不用複製代碼 ```java @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可以參考上面文字繪製圖
相關代碼:
```java @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);
}
``` 效果圖:
裁剪
```java @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中,是不能夠將文字繪製一般的
思路分析: - 繪製兩層(兩層顏色不同),兩層疊加起來 - 然後通過裁剪將上面一層給裁剪掉
在來看看現在代碼是什麼樣子的:
```java //用來記錄當前進度 【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();
}
這裏重點解釋一下上層[需要裁剪的]參數:
java
//裁剪
canvas.clipRect((int) left, 0, (int) left + textWidth * progress, getHeight());
```
- textWidth 需要繪製文字的寬度
- viewWidth 控件寬度的一半
- 文字開始的位置:left = viewWidth - textWidth / 2;
- 文字需要裁剪的位置: 文字的寬度 * progress
通過手勢滑動來控制:
這段代碼並沒有實質性作用,只是來看看效果:
java
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE) {
progress = event.getX() / getWidth();
invalidate();
}
return true;
}
效果圖:
從右到左漸變文字
思路和從左到右繪製是一樣的直接看關鍵代碼:
```java 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代碼:
```kotlin //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
``` 這段代碼,只要學過就懂,不細説了!
重中之重來了:
```java 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次,
優化思路: 當黑色[上層]從左到右滑動的時候,紅色[下層]跟隨着從左到右裁剪
來看看下層繪製的代碼:
```java //繪製下層 不動的 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();
}
``` 效果圖:
原創不易,您的點贊就是對我最大的支持!