您好,登錄后才能下訂單哦!
先給大家展示下效果圖:
代碼已上傳至Github:高仿QQ小紅點,如對您有幫助,歡迎star~感謝
繪制貝塞爾曲線:
主要是當在一定范圍內(nèi)拖拽時算出固定圓和拖拽圓的外切直線以及對應的切點,就可以通過path.quadTo()來繪制二階貝塞爾曲線了~
整體思路:
1、當小紅點靜止時,什么都不做,只需要給自定義小紅點QQBezierView(extends TextView)添加一個.9文件當背景即可
2、當滑動時,通過getRootView()獲得頂級根View,然后new一個DragView ( extends View ) 來繪制各種狀態(tài)時的小紅點,并且通過getRootView().addView()的方式把DragView 加進去,這樣DragView 就可以實現(xiàn)全屏滑動了
實現(xiàn)過程:
自定義QQBezierView ( extends TextView ) 并復寫onTouchEvent來處理各種情況,代碼如下:
@Override public boolean onTouchEvent(MotionEvent event) { //獲得根View View rootView = getRootView(); //獲得觸摸位置在全屏所在位置 float mRawX = event.getRawX(); float mRawY = event.getRawY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //請求父View不攔截 getParent().requestDisallowInterceptTouchEvent(true); //獲得當前View在屏幕上的位置 int[] cLocation = new int[2]; getLocationOnScreen(cLocation); if (rootView instanceof ViewGroup) { //初始化拖拽時顯示的View dragView = new DragView(getContext()); //設置固定圓的圓心坐標 dragView.setStickyPoint(cLocation[0] + mWidth / 2, cLocation[1] + mHeight / 2, mRawX, mRawY); //獲得緩存的bitmap,滑動時直接通過drawBitmap繪制出來 setDrawingCacheEnabled(true); Bitmap bitmap = getDrawingCache(); if (bitmap != null) { dragView.setCacheBitmap(bitmap); //將DragView添加到RootView中,這樣就可以全屏滑動了 ((ViewGroup) rootView).addView(dragView); setVisibility(INVISIBLE); } } break; case MotionEvent.ACTION_MOVE: //請求父View不攔截 getParent().requestDisallowInterceptTouchEvent(true); if (dragView != null) { //更新DragView的位置 dragView.setDragViewLocation(mRawX, mRawY); } break; case MotionEvent.ACTION_UP: getParent().requestDisallowInterceptTouchEvent(false); if (dragView != null) { //手抬起時來判斷各種情況 dragView.setDragUp(); } break; } return true; }
上面代碼注釋已經(jīng)很詳細了,總結一下就是通過內(nèi)部攔截法來請求父View是否攔截分發(fā)事件,并通過event.getRawX()和event.getRawY()來不斷更新DragView的位置,那么DragView都做了哪些事呢,接下來就看一下DragView,DragView是QQBezierView 的一個內(nèi)部View類:
private int mState;//當前紅點的狀態(tài) private static final int STATE_INIT = 0;//默認靜止狀態(tài) private static final int STATE_DRAG = 1;//拖拽狀態(tài) private static final int STATE_MOVE = 2;//移動狀態(tài) private static final int STATE_DISMISS = 3;//消失狀態(tài)
首先聲明了小紅點的四種狀態(tài),靜止狀態(tài),拖拽狀態(tài),移動狀態(tài)和消失狀態(tài)。
在QQBezierView 的onTouchEvent的DOWN事件中調(diào)用了setStickyPoint()方法:
/** * 設置固定圓的圓心和半徑 * @param stickyX 固定圓的X坐標 * @param stickyY 固定圓的Y坐標 */ public void setStickyPoint(float stickyX, float stickyY, float touchX, float touchY) { //分別設置固定圓和拖拽圓的坐標 stickyPointF.set(stickyX, stickyY); dragPointF.set(touchX, touchY); //通過兩個圓點算出圓心距,也是拖拽的距離 dragDistance = MathUtil.getTwoPointDistance(dragPointF, stickyPointF); if (dragDistance <= maxDistance) { //如果拖拽距離小于規(guī)定最大距離,則固定的圓應該越來越小,這樣看著才符合實際 stickRadius = (int) (defaultRadius - dragDistance / 10) < 10 ? 10 : (int) (defaultRadius - dragDistance / 10); mState = STATE_DRAG; } else { mState = STATE_INIT; } }
接著,在QQBezierView 的onTouchEvent的MOVE事件中調(diào)用了setDragViewLocation()方法:
/** * 設置拖拽的坐標位置 * * @param touchX 拖拽時的X坐標 * @param touchY 拖拽時的Y坐標 */ public void setDragViewLocation(float touchX, float touchY) { dragPointF.set(touchX, touchY); //隨時更改圓心距 dragDistance = MathUtil.getTwoPointDistance(dragPointF, stickyPointF); if (mState == STATE_DRAG) { if (isInsideRange()) { stickRadius = (int) (defaultRadius - dragDistance / 10) < 10 ? 10 : (int) (defaultRadius - dragDistance / 10); } else { mState = STATE_MOVE; if (onDragListener != null) { onDragListener.onMove(); } } } invalidate(); }
最后在QQBezierView 的onTouchEvent的UP事件中調(diào)用了setDragUp()方法:
public void setDragUp() { if (mState == STATE_DRAG && isInsideRange()) { //拖拽狀態(tài)且在范圍之內(nèi) startResetAnimator(); } else if (mState == STATE_MOVE) { if (isInsideRange()) { //在范圍之內(nèi) 需要RESET startResetAnimator(); } else { //在范圍之外 消失動畫 mState = STATE_DISMISS; startExplodeAnim(); } } }
最后來看下DragView的onDraw方法,拖拽時的貝塞爾曲線以及拖拽滑動時的狀態(tài)都是通過onDraw實現(xiàn)的:
@Override protected void onDraw(Canvas canvas) { if (isInsideRange() && mState == STATE_DRAG) { mPaint.setColor(Color.RED); //繪制固定的小圓 canvas.drawCircle(stickyPointF.x, stickyPointF.y, stickRadius, mPaint); //首先獲得兩圓心之間的斜率 Float linK = MathUtil.getLineSlope(dragPointF, stickyPointF); //然后通過兩個圓心和半徑、斜率來獲得外切線的切點 PointF[] stickyPoints = MathUtil.getIntersectionPoints(stickyPointF, stickRadius, linK); dragRadius = (int) Math.min(mWidth, mHeight) / 2; PointF[] dragPoints = MathUtil.getIntersectionPoints(dragPointF, dragRadius, linK); mPaint.setColor(Color.RED); //二階貝塞爾曲線的控制點取得兩圓心的中點 controlPoint = MathUtil.getMiddlePoint(dragPointF, stickyPointF); //繪制貝塞爾曲線 mPath.reset(); mPath.moveTo(stickyPoints[0].x, stickyPoints[0].y); mPath.quadTo(controlPoint.x, controlPoint.y, dragPoints[0].x, dragPoints[0].y); mPath.lineTo(dragPoints[1].x, dragPoints[1].y); mPath.quadTo(controlPoint.x, controlPoint.y, stickyPoints[1].x, stickyPoints[1].y); mPath.lineTo(stickyPoints[0].x, stickyPoints[0].y); canvas.drawPath(mPath, mPaint); } if (mCacheBitmap != null && mState != STATE_DISMISS) { //繪制緩存的Bitmap canvas.drawBitmap(mCacheBitmap, dragPointF.x - mWidth / 2, dragPointF.y - mHeight / 2, mPaint); } if (mState == STATE_DISMISS && explodeIndex < explode_res.length) { //繪制小紅點消失時的爆炸動畫 canvas.drawBitmap(bitmaps[explodeIndex], dragPointF.x - mWidth / 2, dragPointF.y - mHeight / 2, mPaint); } }
PS:最開始使用的是 Android:clipChildren=”false” 這個屬性,如果父View只是一個普通的ViewGroup(如LinearLayout、RelativeLayout等),此時在父View中設置android:clipChildren=”false”后,子View就可以超出自己的范圍,在ViewGroup中也可以滑動了,此時也沒問題;但是當是RecycleView時,只要ItemView設置了background屬性,滑動時的DragView就會顯示在background的下面了,好蛋疼~如有知其原因的還望不吝賜教~
最后再貼下源碼下載地址:Android高仿QQ小紅點
以上所述是小編給大家介紹的Android高仿QQ小紅點功能,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對億速云網(wǎng)站的支持!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。