Android仿QQ消息提示點(diǎn)拖拽功能
很久以前,發(fā)現(xiàn)QQ有一個(gè)很有趣的功能,就是未讀消息的紅點(diǎn)是可以拖拽的,而且在任何地方都可以隨意拖拽,并且有一個(gè)彈性的動(dòng)畫,非常有趣,而且也是一個(gè)非常方便的功能,于是總想仿制一個(gè),雖說仿制,但也只是他的拖拽功能,彈性效果還是能力有限。
不多說 先上效果
一個(gè)自定義的view 使用方式也很簡(jiǎn)單
<com.weizhenbin.show.widget.VanishView android:layout_width="30dp" android:layout_height="30dp" android:text="5" android:layout_alignParentBottom="true" android:gravity="center" android:textColor="#fff" android:id="@+id/vv" android:layout_marginBottom="35dp" android:layout_marginLeft="80dp" android:background="@drawable/shape_red_bg"/>
然后先看下源碼
** * Created by weizhenbin on 16/6/1. * <p/> * 一個(gè)可以隨意拖動(dòng)的view */ public class VanishView extends TextView { private Context context; /**窗口管理器*/ private WindowManager windowManager; /**用來存儲(chǔ)鏡像的imageview*/ private ImageView iv; /** 狀態(tài)欄高度*/ private int statusHeight = 0; /**按下的坐標(biāo)x 相對(duì)于view自身*/ private int dx = 0; /**按下的坐標(biāo)y 相對(duì)于view自身*/ private int dy = 0; /**鏡像bitmap*/ private Bitmap tmp; /**按下的坐標(biāo)x 相對(duì)于屏幕*/ private float downX = 0; /**按下的坐標(biāo)y 相對(duì)于屏幕*/ private float downY = 0; /**屬性動(dòng)畫 用于回彈效果*/ private ValueAnimator animator; /**窗口參數(shù)*/ private WindowManager.LayoutParams mWindowLayoutParams; /**接口對(duì)象*/ private OnListener listener; public VanishView(Context context) { super(context); init(context); } public VanishView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public VanishView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context) { this.context = context; windowManager = ((Activity) context).getWindowManager(); statusHeight = getStatusHeight(context); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: dx = (int) event.getX(); dy = (int) event.getY(); downX = event.getRawX(); downY = event.getRawY(); addWindow(context, event.getRawX(), event.getRawY()); setVisibility(INVISIBLE); break; case MotionEvent.ACTION_MOVE: mWindowLayoutParams.x = (int) (event.getRawX() - dx); mWindowLayoutParams.y = (int) (event.getRawY() - statusHeight - dy); windowManager.updateViewLayout(iv, mWindowLayoutParams); break; case MotionEvent.ACTION_UP: int distance=distance(new MyPoint(event.getRawX(), event.getRawY()), new MyPoint(downX, downY)); if(distance<400) { scroll(new MyPoint(event.getRawX(), event.getRawY()), new MyPoint(downX, downY)); }else { if(listener!=null){ listener.onDismiss(); } windowManager.removeView(iv); } break; } return true; } /** * 構(gòu)建一個(gè)窗口 用于存放和移動(dòng)鏡像 * */ private void addWindow(Context context, float downX, float dowmY) { mWindowLayoutParams = new WindowManager.LayoutParams(); mWindowLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT; mWindowLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT; iv = new ImageView(context); mWindowLayoutParams.format = PixelFormat.RGBA_8888; mWindowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT; mWindowLayoutParams.x = (int) (downX - dx); mWindowLayoutParams.y = (int) (dowmY - statusHeight - dy); //獲取view的鏡像bitmap this.setDrawingCacheEnabled(true); tmp = Bitmap.createBitmap(this.getDrawingCache()); //釋放緩存 this.destroyDrawingCache(); iv.setImageBitmap(tmp); windowManager.addView(iv, mWindowLayoutParams); } /** * 使用屬性動(dòng)畫 實(shí)現(xiàn)緩慢回彈效果 * */ private void scroll(MyPoint start, MyPoint end) { animator = ValueAnimator.ofObject(new MyTypeEvaluator(), start, end); animator.setDuration(200); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { MyPoint point = (MyPoint) animation.getAnimatedValue(); mWindowLayoutParams.x = (int) (point.x - dx); mWindowLayoutParams.y = (int) (point.y - statusHeight - dy); windowManager.updateViewLayout(iv, mWindowLayoutParams); } }); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); windowManager.removeView(iv); setVisibility(VISIBLE); } }); animator.start(); } /** * 計(jì)算兩點(diǎn)的距離 */ private int distance(MyPoint point1, MyPoint point2) { int distance = 0; if (point1 != null && point2 != null) { float dx = point1.x - point2.x; float dy = point1.y - point2.y; distance = (int) Math.sqrt(dx * dx + dy * dy); } return distance; } /** * 獲取狀態(tài)欄的高度 */ private static int getStatusHeight(Context context) { int statusHeight = 0; Rect localRect = new Rect(); ((Activity) context).getWindow().getDecorView().getWindowVisibleDisplayFrame(localRect); statusHeight = localRect.top; if (0 == statusHeight) { Class<?> localClass; try { localClass = Class.forName("com.android.internal.R$dimen"); Object localObject = localClass.newInstance(); int i5 = Integer.parseInt(localClass.getField("status_bar_height").get(localObject).toString()); statusHeight = context.getResources().getDimensionPixelSize(i5); } catch (Exception e) { e.printStackTrace(); } } return statusHeight; } class MyPoint { float x; float y; public MyPoint(float x, float y) { this.x = x; this.y = y; } @Override public String toString() { return "MyPoint{" + "x=" + x + ", y=" + y + '}'; } } class MyTypeEvaluator implements TypeEvaluator<MyPoint> { @Override public MyPoint evaluate(float fraction, MyPoint startValue, MyPoint endValue) { MyPoint point = startValue; point.x = startValue.x + fraction * (endValue.x - startValue.x); point.y = startValue.y + fraction * (endValue.y - startValue.y); return point; } } /**事件回調(diào)借口*/ public interface OnListener{ void onDismiss(); } public void setListener(OnListener listener) { this.listener = listener; }
實(shí)現(xiàn)這一功能其實(shí)也不難,這個(gè)功能涉及到以下幾個(gè)知識(shí)點(diǎn)
使用WindowManager添加一個(gè)view
使用ValueAnimator屬性動(dòng)畫實(shí)現(xiàn)回彈效果
getX和getRawX,getY和getRawY的區(qū)別
1.使用WindowManager添加一個(gè)view
/** * 構(gòu)建一個(gè)窗口 用于存放和移動(dòng)鏡像 * */ private void addWindow(Context context, float downX, float dowmY) { mWindowLayoutParams = new WindowManager.LayoutParams(); mWindowLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT; mWindowLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT; iv = new ImageView(context); mWindowLayoutParams.format = PixelFormat.RGBA_8888; mWindowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT; mWindowLayoutParams.x = (int) (downX - dx); mWindowLayoutParams.y = (int) (dowmY - statusHeight - dy); //獲取view的鏡像bitmap this.setDrawingCacheEnabled(true); tmp = Bitmap.createBitmap(this.getDrawingCache()); //釋放緩存 this.destroyDrawingCache(); iv.setImageBitmap(tmp); windowManager.addView(iv, mWindowLayoutParams); }
這一步是為了投影一個(gè)鏡像來達(dá)到拖動(dòng)view的一個(gè)假像效果,使用imageview來顯示。這里為了使投影沒用偏移需要了解getX getRawX getY getRawY的區(qū)別
getX和getY 是相對(duì)于view自身的,getRawX和getRawY是像對(duì)屏幕的,這里還要扣除掉狀態(tài)欄的高度。
2.移動(dòng)
windowManager.updateViewLayout(iv, mWindowLayoutParams);
3.使用ValueAnimator屬性動(dòng)畫實(shí)現(xiàn)回彈效果
這里自定義了TypeEvaluator實(shí)現(xiàn)點(diǎn)的位移動(dòng)畫
class MyTypeEvaluator implements TypeEvaluator<MyPoint> { @Override public MyPoint evaluate(float fraction, MyPoint startValue, MyPoint endValue) { MyPoint point = startValue; point.x = startValue.x + fraction * (endValue.x - startValue.x); point.y = startValue.y + fraction * (endValue.y - startValue.y); return point; } } /** * 使用屬性動(dòng)畫 實(shí)現(xiàn)緩慢回彈效果 * */ private void scroll(MyPoint start, MyPoint end) { animator = ValueAnimator.ofObject(new MyTypeEvaluator(), start, end); animator.setDuration(200); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { MyPoint point = (MyPoint) animation.getAnimatedValue(); mWindowLayoutParams.x = (int) (point.x - dx); mWindowLayoutParams.y = (int) (point.y - statusHeight - dy); windowManager.updateViewLayout(iv, mWindowLayoutParams); } }); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); windowManager.removeView(iv); setVisibility(VISIBLE); } }); animator.start(); }
通過屬性動(dòng)畫實(shí)現(xiàn)一個(gè)回彈效果
4.觸發(fā)消失的時(shí)機(jī)
/** * 計(jì)算兩點(diǎn)的距離 */ private int distance(MyPoint point1, MyPoint point2) { int distance = 0; if (point1 != null && point2 != null) { float dx = point1.x - point2.x; float dy = point1.y - point2.y; distance = (int) Math.sqrt(dx * dx + dy * dy); } return distance; }
計(jì)算兩點(diǎn)之間的距離來觸發(fā)一個(gè)回調(diào)事件。
int distance=distance(new MyPoint(event.getRawX(), event.getRawY()), new MyPoint(downX, downY)); if(distance<400) { scroll(new MyPoint(event.getRawX(), event.getRawY()), new MyPoint(downX, downY)); }else { if(listener!=null){ listener.onDismiss(); } windowManager.removeView(iv); }
代碼分析就到這里,實(shí)現(xiàn)這個(gè)功能的核心代碼都在這里。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Flutter 系統(tǒng)是如何實(shí)現(xiàn)ExpansionPanelList的示例代碼
Flutter組件有一個(gè)很大的特色,那就是很多復(fù)雜的組件都是通過一個(gè)一個(gè)小組件拼裝而成的,今天就來說說系統(tǒng)的ExpansionPanelList是如何實(shí)現(xiàn)的,需要的朋友可以參考下2020-05-05Android中ViewFlipper的使用及設(shè)置動(dòng)畫效果實(shí)例詳解
這篇文章主要介紹了Android中ViewFlipper的使用及設(shè)置動(dòng)畫效果的方法,以實(shí)例形式較為詳細(xì)的分析了ViewFlipper的功能、原理及設(shè)置與使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10android實(shí)現(xiàn)百度地圖自定義彈出窗口功能
這篇文章主要介紹了android實(shí)現(xiàn)百度地圖自定義彈出窗口的功能,大家參考使用吧2013-11-11Flutter實(shí)現(xiàn)資源下載斷點(diǎn)續(xù)傳的示例代碼
在項(xiàng)目開發(fā)中,特別是C端的產(chǎn)品,資源下載實(shí)現(xiàn)斷點(diǎn)續(xù)傳是非常有必要的。今天我們不講過多原理的知識(shí),分享下簡(jiǎn)單實(shí)用的資源斷點(diǎn)續(xù)傳2022-07-07Android加載對(duì)話框同時(shí)異步執(zhí)行實(shí)現(xiàn)方法
Android中通過子線程連接網(wǎng)絡(luò)獲取資料,同時(shí)顯示加載進(jìn)度對(duì)話框給用戶的操作2012-11-11Android 8.0 讀取內(nèi)部和外部存儲(chǔ)以及外置SDcard的方法
今天小編就為大家分享一篇Android 8.0 讀取內(nèi)部和外部存儲(chǔ)以及外置SDcard的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-08-08Flutter實(shí)現(xiàn)底部導(dǎo)航欄
這篇文章主要為大家詳細(xì)介紹了Flutter實(shí)現(xiàn)底部導(dǎo)航欄的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-02-02