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-05
Android中ViewFlipper的使用及設(shè)置動(dòng)畫效果實(shí)例詳解
這篇文章主要介紹了Android中ViewFlipper的使用及設(shè)置動(dòng)畫效果的方法,以實(shí)例形式較為詳細(xì)的分析了ViewFlipper的功能、原理及設(shè)置與使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10
android實(shí)現(xiàn)百度地圖自定義彈出窗口功能
這篇文章主要介紹了android實(shí)現(xiàn)百度地圖自定義彈出窗口的功能,大家參考使用吧2013-11-11
Flutter實(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-07
Android加載對(duì)話框同時(shí)異步執(zhí)行實(shí)現(xiàn)方法
Android中通過子線程連接網(wǎng)絡(luò)獲取資料,同時(shí)顯示加載進(jìn)度對(duì)話框給用戶的操作2012-11-11
Android 8.0 讀取內(nèi)部和外部存儲(chǔ)以及外置SDcard的方法
今天小編就為大家分享一篇Android 8.0 讀取內(nèi)部和外部存儲(chǔ)以及外置SDcard的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-08-08
Flutter實(shí)現(xiàn)底部導(dǎo)航欄
這篇文章主要為大家詳細(xì)介紹了Flutter實(shí)現(xiàn)底部導(dǎo)航欄的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-02-02

