close

作者:android超級兵原文:https://juejin.cn/post/6995153251221897252

在滑動的過程中,漸變文字會隨着 ViewPager 的滑動而變化

1. 繪製文字與BaseLine思考

先來看看最初版代碼:

publicclassGradualChangeTvextendsAppCompatTextView{publicPaintmPaint=newPaint();publicfinalStringtext="android超級兵";publicGradualChangeTv(Contextcontext){this(context,null);}publicGradualChangeTv(Contextcontext,AttributeSetattrs){this(context,attrs,0);}publicGradualChangeTv(Contextcontext,AttributeSetattrs,intdefStyleAttr){super(context,attrs,defStyleAttr);mPaint.setColor(Color.RED);//抗鋸齒mPaint.setAntiAlias(true);}@OverrideprotectedvoidonDraw(Canvascanvas){super.onDraw(canvas);/**繪製文字*參數一:繪製文字*參數二:x軸開始位置*參數三:y軸開始位置*參數四:畫筆*/canvas.drawText(text,0,0,mPaint);}}

就是簡單的繪製了一行字

疑問: 為什麼這裡要繼承自AppCompatTextView 而不是View ?
答: 偷個懶而已,因為不用在我來測量View,直接用父類的就行

來看看效果順便也看看布局:

出現問題: 文字並沒有顯示
答:因為文字坐標系和屏幕坐標系不一樣,文字坐標系是從BaseLine線開始計算的

先來回顧一下屏幕的坐標系

再來看看文字的坐標系

(圖片來自於網絡)

再來思考一下文字是為什麼不顯示的:

虛線為 BaseLine

如果此時我把字體放大到100,看一看我能不能看到文字

再一次證明了文字是從BaseLine線開始繪製

2. 文字居中

可以用兩條輔助線,水平線與垂直線.然後在來看文字是否居中

代碼 (底部會給出完整代碼,這裡看思路即可)

@OverrideprotectedvoidonDraw(Canvascanvas){super.onDraw(canvas);//獲取當前控件的寬高intviewWidth=getWidth()/2;intviewHeight=getHeight()/2;/**繪製文字*參數一:繪製文字*參數二:x軸開始位置*參數三:y軸開始位置*參數四:畫筆*/canvas.drawText(text,viewWidth,viewHeight,mPaint);//繪製居中線drawCenterLine(canvas,viewWidth,viewHeight);}privatevoiddrawCenterLine(Canvascanvas,intviewWidth,intviewHeight){//垂直線canvas.drawLine(viewWidth,0,viewWidth,getHeight(),mPaint);//水平線canvas.drawLine(0,viewHeight,getWidth(),viewHeight,mPaint);}

效果圖:

可以看出,還是上面說的那個問題,文字繪製是基於 baseLine 線來繪製的.

文字居中思路:

通過 mPaint.measureText(text) 獲取文字寬
通過 mPaint.descent() + mPaint.ascent(); 獲取文字高
然後控件各取一半,讓控件減去即可

這裡的descent和ascent可以參考上面文字繪製圖

相關代碼:

@OverrideprotectedvoidonDraw(Canvascanvas){super.onDraw(canvas);//文字寬度floattextWidth=mPaint.measureText(text);//文字高度floattextHeight=mPaint.descent()+mPaint.ascent();//獲取當前控件的寬高intviewWidth=getWidth()/2;intviewHeight=getHeight()/2;canvas.drawText(text,viewWidth-textWidth/2,viewHeight-textHeight/2,mPaint);//繪製居中線drawCenterLine(canvas,viewWidth,viewHeight);}

效果圖:

3. 裁剪@OverrideprotectedvoidonDraw(Canvascanvas){super.onDraw(canvas);//文字寬度floattextWidth=mPaint.measureText(text);//文字高度floattextHeight=mPaint.descent()+mPaint.ascent();//獲取當前控件的寬高的一半intviewWidth=getWidth()/2;intviewHeight=getHeight()/2;//裁剪drawClip(canvas,viewWidth,viewHeight,textWidth,textHeight);//繪製居中線drawCenterLine(canvas,viewWidth,viewHeight);}privatevoiddrawClip(Canvascanvas,intviewWidth,intviewHeight,floattextWidth,floattextHeight){mPaint.setColor(Color.BLACK);canvas.save();//繪製文字X軸的位置floatleft=viewWidth-textWidth/2;//繪製文字Y軸的位置floatright=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() 可以理解為將當前繪製的東西當作一個新的圖層

來看看效果圖:

代碼注釋很清晰;就不過多解釋了

4. 從左到右漸變文字

眾所周知,在android中,是不能夠將文字繪製一般的

思路分析:

繪製兩層(兩層顏色不同),兩層疊加起來
然後通過裁剪將上面一層給裁剪掉

在來看看現在代碼是什麼樣子的:

//用來記錄當前進度【0-1】floatprogress=0.3f;@OverrideprotectedvoidonDraw(Canvascanvas){super.onDraw(canvas);//文字寬度floattextWidth=mPaint.measureText(text);//文字高度floattextHeight=mPaint.descent()+mPaint.ascent();//獲取當前控件的寬高的一半intviewWidth=getWidth()/2;intviewHeight=getHeight()/2;//繪製底層drawBottom(canvas,viewWidth,viewHeight,textWidth,textHeight);//繪製上層【顏色漸變的】drawUp(canvas,viewWidth,viewHeight,textWidth,textHeight);//繪製居中線drawCenterLine(canvas,viewWidth,viewHeight);}//繪製上層【漸變的】privatevoiddrawUp(Canvascanvas,intviewWidth,intviewHeight,floattextWidth,floattextHeight){mPaint.setColor(Color.BLACK);canvas.save();//繪製文字X軸的位置floatleft=viewWidth-textWidth/2;//繪製文字Y軸的位置floatright=viewHeight-textHeight/2;//裁剪canvas.clipRect((int)left,0,(int)left+textWidth*progress,getHeight());/**繪製文字*參數一:繪製文字*參數二:x軸開始位置*參數三:y軸開始位置*參數四:畫筆*/canvas.drawText(text,left,right,mPaint);canvas.restore();}//繪製下層不動的privatevoiddrawBottom(Canvascanvas,intviewWidth,intviewHeight,floattextWidth,floattextHeight){mPaint.setColor(Color.RED);//文字顏色canvas.save();//文字開始位置floatleft=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")@OverridepublicbooleanonTouchEvent(MotionEventevent){if(event.getAction()==MotionEvent.ACTION_MOVE){progress=event.getX()/getWidth();invalidate();}returntrue;}

效果圖:

5. 從右到左漸變文字

思路和從左到右繪製是一樣的直接看關鍵代碼:

privatevoiddrawRightToLeft(Canvascanvas,intviewWidth,intviewHeight,floattextWidth,floattextHeight){mPaint.setColor(Color.GREEN);/**這裡 left和right能夠在此抽取出來,不過這樣寫很易懂,有需求自己弄吧!!!*/canvas.save();//繪製文字X軸的位置【文字開始的位置】floattextX=viewWidth-textWidth/2;//繪製文字Y軸的位置floattextY=viewHeight-textHeight/2;//文字結束的位置floatend=viewWidth+mPaint.measureText(text)/2;canvas.clipRect(end,0,textX+textWidth*(1-progress),getHeight());canvas.drawText(text,textX,textY,mPaint);canvas.restore();}@SuppressLint("ClickableViewAccessibility")@OverridepublicbooleanonTouchEvent(MotionEventevent){if(event.getAction()==MotionEvent.ACTION_MOVE){if(type==GradualChangeTextView.GRADUAL_CHANGE_RIGHT){//從右到左滑動progress=1-event.getX()/getWidth();}elseif(type==GradualChangeTextView.GRADUAL_CHANGE_LEFT){//從左到右滑動progress=event.getX()/getWidth();}invalidate();}returntrue;}

效果圖:

最後在添加兩個按鈕來完全測試一下代碼有沒有問題:

完完全全沒有問題!

6. 最終實現效果(漸變滑動)

先來看看布局:

布局簡單的很,就是文字和ViewPager

大致看看ViewPager代碼:

//text1..text4是控件idvaltextList=listOf(text1,text2,text3,text4)vallist=listOf(HomeFragment(),MyFragment(),TestFragment(),SettingFragment())valviewPagerAdapter=ViewPagerAdapter(supportFragmentManager,list)viewPager.adapter=viewPagerAdapter//默認選擇第一頁viewPager.currentItem=1//默認選中textList[viewPager.currentItem].percent=1f

這段代碼,只要學過就懂,不細說了!

重中之重來了:

viewPager.addOnPageChangeListener(object:ViewPager.OnPageChangeListener{overridefunonPageScrolled(position:Int,positionOffset:Float,positionOffsetPixels:Int,){if(positionOffset>0){valleft=textList[position]valright=textList[position+1]//從右到左滑動left.setSlidingPosition(GradualChangeTextView.GRADUAL_CHANGE_RIGHT)//從左到右滑動right.setSlidingPosition(GradualChangeTextView.GRADUAL_CHANGE_LEFT)//當前頁面取反[從右到左]left.percent=1-positionOffset//下一個頁面正常[從左到右]right.percent=positionOffset}}overridefunonPageSelected(position:Int){}overridefunonPageScrollStateChanged(state:Int){//當ViewPage結束的時候,重新設置一下狀態[不設置的話滑動太快,會導致'殘影']textList.forEach{if(it.tag==textList[viewPager.currentItem].tag){it.percent=1f}else{it.percent=0f}}}})

來看看效果:

7. 過度繪製極限優化
原色 – 沒有被過度繪製 – 這部分的像素點只在屏幕上繪製了一次。
藍色 – 1次過度繪製– 這部分的像素點只在屏幕上繪製了兩次。
綠色 – 2次過度繪製 – 這部分的像素點只在屏幕上繪製了三次。
粉色 – 3次過度繪製 – 這部分的像素點只在屏幕上繪製了四次。
紅色 – 4次過度繪製 – 這部分的像素點只在屏幕上繪製了五次。

先來看看沒有優化的效果:

可以看到,在繪製的過程中,因為是兩層,那麼就繪製了2次,

優化思路: 當黑色[上層]從左到右滑動的時候,紅色[下層]跟隨着從左到右裁剪

來看看下層繪製的代碼:

//繪製下層不動的privatevoiddrawBottom(Canvascanvas,intviewWidth,intviewHeight,floattextWidth,floattextHeight){mPaint.setColor(Color.RED);canvas.save();//繪製文字X軸的位置[文字開始的位置]floattextX=viewWidth-textWidth/2;//繪製文字Y軸的位置floattextY=viewHeight-textHeight/2;//跟隨者上層裁剪canvas.clipRect((int)textX+textWidth*progress,0,textWidth+viewWidth,getHeight());/**繪製文字*參數一:繪製文字*參數二:x軸開始位置*參數三:y軸開始位置*參數四:畫筆*/canvas.drawText(text,textX,textY,mPaint);canvas.restore();}

效果圖:

完整代碼:https://gitee.com/lanyangyangzzz/android_ui

-- End --


推薦閱讀

使用 Compose 搞一個貪吃蛇遊戲

天道酬勤,進字節了

妙啊!Jetpack Compose 實現波浪加載

加好友進交流群,技術乾貨聊不停

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()