android之View和LinearLayout的重寫(實現背景氣泡和波紋效果)

來源:互聯網
上載者:User

標籤:android

前兩天看了仿android L裡面水波紋效果的兩篇部落格

Android L中水波紋點擊效果的實現

Android自訂群組件系列【14】——Android5.0按鈕波紋效果實現


第一篇是實現了一個水波紋布局,放在裡面的所有控制項點擊後都會出現波紋效果

第二篇是實現了一個水波紋view,點擊之後自身會出現波紋效果


根據對這兩篇部落格的理解,我自己實現了一個類似的東西,沒找到合適的錄屏軟體,只好把波紋的速度調快了很多才錄下來,能看出來啥意思,不調速度的話還算比較優雅。


就是像上面這樣一個控制項,裡面的背景用的是一個重寫的TextView,背景就一直有一個不斷“呼吸”的氣泡。

這裡就聯絡前面兩篇部落格(建議先去看下),介紹下在View和ViewGroup中實現背景動畫,同時紀錄下自己對裡面知識點的理解。

就暫時把這種效果命名成會呼吸的氣泡。(後來發現這個圖不動,好吧不知為啥不浪費時間了,就自己想象下這個整個背景有一個圓,不停的放大縮小放大縮小,圓心隨機,這個剛好是隨機到左上方位置了)


首先是ViewGroup

這裡就直接以第一篇部落格為例,紀錄下自己的理解

1.擷取座標,這個是比較重要的一步,之後判斷點擊的控制項還有繪製波紋都需要用到

@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {super.onLayout(changed, l, t, r, b);this.getLocationOnScreen(mLocationInScreen);}

這裡擷取到的是當前布局左上方在整個螢幕中的座標

2.擷取點擊到的控制項,這裡的擷取就需要根據座標挨個判斷了,在dispatchTouchEvent方法裡面會傳入當前點擊事件MotionEvent,他會帶入當前點擊的座標,這裡有兩種擷取方式,一種是getX,一種是getRawX,在view的座標系裡面說到這兩種的區別了,為了計算點擊事件,這裡要擷取的是相對螢幕的座標,其實在布局的重寫裡面整片都是使用相對螢幕的座標,因為布局會出現嵌套,嵌套之後相對座標就不對了,所以全都使用相對螢幕的座標去計算。擷取到點擊事件的座標,就可以拿座標去找到所點擊的控制項了。整個過程部落格裡面已經很詳細了。

3.拿到點擊的控制項之後就要在控制項上面繪製波紋了,這裡代碼還是貼一下

// view繪製流程:先繪製背景,再繪製自己(onDraw),接著繪製子項目(dispatchDraw),最後繪製一些裝飾等比如捲軸(onDrawScrollBars)// 為了防止繪製繪製的子項目把波紋擋住,這裡選擇在子項目繪製完成再繪製波紋@Overrideprotected void dispatchDraw(Canvas canvas) {super.dispatchDraw(canvas);if (!mShouldDoAnimation || mTargetWidth < 0 || mTouchTarget == null) {return;}if (mRevealRadius > mMinBetweenWidthAndHeight / 2) {// 當半徑超過短邊之後,增加擴散速度儘快完成擴散mRevealRadius += mRevealRadiusGap * 4;} else {// 波紋當半徑遞增擴散mRevealRadius += mRevealRadiusGap;}this.getLocationOnScreen(mLocationInScreen);// 擷取本布局的座標---1??int[] location = new int[2];mTouchTarget.getLocationOnScreen(location);// 擷取點擊控制項的座標---2??int left = location[0] - mLocationInScreen[0];//---3??int top = location[1] - mLocationInScreen[1];int right = left + mTouchTarget.getMeasuredWidth();int bottom = top + mTouchTarget.getMeasuredHeight();canvas.save();canvas.clipRect(left, top, right, bottom);//---4??canvas.drawCircle(mCenterX, mCenterY, mRevealRadius, mPaint);canvas.restore();if (mRevealRadius <= mMaxRevealRadius) {postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);//---5??} else if (!mIsPressed) {mShouldDoAnimation = false;postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);}}

整個繪製的過程就是上面這樣,還是重點關注下畫布的切割和座標的計算,繪製流程和半徑的計算之類的看看就明白了

1??擷取的是布局左上方的座標

2??擷取的是控制項左上方的座標

3??控制項的座標減去布局的座標,就是布局在控制項上的相對座標,這裡的相對座標其實就是getLeft和getTop的概念,但是不能這麼用,因為有可能控制項與布局之間還有嵌套別的布局

4??在3??中已經擷取到了控制項相對於布局的座標,這裡就在布局的畫布上把控制項對應的位置切割下來,然後在上面畫圓,切割是為了提高效能

5??這裡畫完圓之後要馬上畫下一個半徑更大的圓,從而達到擴散的效果,所以要postInvalidateDelayed去重新整理,重新整理的時候只重新整理控制項所對應的那個地區,也是為了提高效能

基本上就這些吧,原博已經講得比較詳細了,我這裡只是針對自己的理解紀錄下。


然後是View

看一下分幾個步驟

1.擷取當前控制項的寬高資訊(用來初始化氣泡的半徑等資訊)

2.擷取點擊事件(用來作為氣泡的圓心,在沒有點擊事件的時候它是隨機座標作為圓心的,點擊則移動到點擊的位置)

3.繪製氣泡

下面是重寫的BreathTextView代碼

public class JasonBreathTextView extends TextView {private JasonBreathCircle breathCircle;public JasonBreathTextView(Context context) {super(context);}public JasonBreathTextView(Context context, AttributeSet attrs) {super(context, attrs);breathCircle = new JasonBreathCircle(context);}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {// TODO Auto-generated method stubsuper.onSizeChanged(w, h, oldw, oldh);breathCircle.initParameters(this);}@Overridepublic boolean onTouchEvent(MotionEvent event) {breathCircle.setCircleCenter((int) event.getX(), (int) event.getY());return super.onTouchEvent(event);}@Overrideprotected void onDraw(Canvas canvas) {breathCircle.draw(canvas);super.onDraw(canvas);}/** * 開始水紋效果 */public void startReveal() {breathCircle.start();}/** * 停止水紋效果 */public void stopReveal() {breathCircle.stop();}}

這個就是圖中使用的繼承自TextView的控制項。

為了使用方便,我把氣泡的實現和控制項的實現分開了,這樣之後不管實現哪種View都可以直接使用分離出來的氣泡類,使用方法就像上面這樣,只需要把view控制項本身傳給氣泡,氣泡就會在控制項上繪製了。

氣泡BreathCircle的代碼如下:

public class JasonBreathCircle {private static int DIFFUSE_GAP = 2; // 擴散半徑增量private static final int INVALIDATE_DURATION = 10; // 每次重新整理的時間間隔private Context mContext;private boolean needToDrawReveal = false;// 繪畫標誌位// 圓形自身的一些屬性private boolean isLargerMode = true;// 呼吸模式private Paint mPaintReveal;// 畫筆private int mCircleCenterX;// 圓心xprivate int mCircleCenterY;// 圓心yprivate int mCurRadius;// 當前半徑private int mMaxRadius;// 最大半徑// 依附的控制項的一些屬性,利用高度寬度計算當前觸摸點的位置private View mParentView;// 依附的控制項private int mParentHeight;// 控制項高度private int mParentWidth;// 控制項寬度// ================初始化方法(必須調用)===============/** * 執行個體化一個圓,之後要調用initParameters初始化該圓的屬性,再之後就可以draw了 *  * @param context */public JasonBreathCircle(Context context) {mContext = context;initPaint();}/** * 傳入view,用來初始化座標,半徑,預設以中心為圓心開始畫圓 *  * @param view */public void initParameters(View view) {this.mParentView = view;// 擷取當前依附控制項的屬性mParentHeight = mParentView.getHeight();mParentWidth = mParentView.getWidth();// 初始化圓的屬性mMaxRadius = (int) Math.hypot(view.getHeight(), view.getWidth()) / 2;// 控制項的寬度高度求出初始圓心mCircleCenterX = mParentWidth / 2;mCircleCenterY = mParentHeight / 2;}/** * 傳入畫布 *  * @param canvas */public void draw(Canvas canvas) {if (needToDrawReveal) {canvas.save();canvas.drawCircle(mCircleCenterX, mCircleCenterY, mCurRadius,mPaintReveal);canvas.restore();if (isLargerMode && mCurRadius < mMaxRadius) {mCurRadius += DIFFUSE_GAP;// 波紋遞增postRevealInvalidate();} else if (mCurRadius > 0 && !isLargerMode) {// 畫完一個周期從頭再畫mCurRadius -= DIFFUSE_GAP;// 波紋遞增postRevealInvalidate();} else {// 轉換模式isLargerMode = !isLargerMode;// 隨機播放座標作為圓心,從0到最右邊中間取x,從0到底邊取ysetCircleCenter(JasonRandomUtil.nextInt(0, mParentWidth),JasonRandomUtil.nextInt(0, mParentHeight));// 圓心更換後,縮小前,把當前半徑設為最大,防止邊上出現空白覆蓋不滿if (!isLargerMode) {mCurRadius = mMaxRadius;}postRevealInvalidate();}}}// ===============對外介面===============/** * 開始呼吸 */public void start() {if (needToDrawReveal) {return;}needToDrawReveal = true;postRevealInvalidate();}/** * 停止呼吸 */public void stop() {if (!needToDrawReveal) {return;}needToDrawReveal = false;reset();postRevealInvalidate();}/** * 設定圓心 *  * @param x * @param y */public void setCircleCenter(int x, int y) {mCircleCenterX = x;mCircleCenterY = y;mMaxRadius = JasonRadiusUtil.getMaxRadius(mCircleCenterX,mCircleCenterY, mParentWidth, mParentHeight);}/** * 設定畫圓為空白心還是實心,預設實心 *  * @param isHollow */public void setHollow(boolean isHollow) {mPaintReveal.setStyle(isHollow ? Paint.Style.STROKE : Paint.Style.FILL);}// ================內部實現===============/** * 重設 */private void reset() {mCurRadius = 0;isLargerMode = true;}/** * 初始化畫筆 */private void initPaint() {mPaintReveal = new Paint();mPaintReveal.setColor(mContext.getResources().getColor(R.color.jason_bg_common_green_light));mPaintReveal.setAntiAlias(true);}/** * 重繪 */private void postRevealInvalidate() {mParentView.postInvalidateDelayed(INVALIDATE_DURATION);}}

擷取控制項的寬高資訊是在onSizeChanged這個方法中,這裡會有寬高資訊可以去擷取

擷取點擊事件是在onTouchEvent裡面

最後繪製這裡選擇了onDraw方法

如果看過前面兩篇部落格了,這裡還是紀錄了兩個地方:

1??關於繪製其實有好幾個方法可以選用,看下view繪製流程:先繪製背景,再繪製自己(onDraw),接著繪製子項目(dispatchDraw),最後繪製一些裝飾等比如捲軸(onDrawScrollBars)

因為這裡是要把繪製出來的氣泡做背景,所以要在氣泡繪製完成才去繪製view自身的一些東西,所以在onDraw裡面最合適了

2??關於座標,由於受前面第一篇部落格裡面布局重寫時對座標處理的影響,在這裡浪費了些時間,其實在view的重寫裡面,重繪的時候使用座標,只需要知道view的寬高就夠了,因為onDraw傳進來的canvas就是控制項本身的大小,

所以不需要像布局裡面那樣對畫布進行裁剪,只要直接在上面畫就行了。座標系直接自己按照寬高去建立就行了,左上方是原點。


jason0539

微博:http://weibo.com/2553717707

部落格:http://blog.csdn.net/jason0539(轉載請說明出處)

android之View和LinearLayout的重寫(實現背景氣泡和波紋效果)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.