Android自定義View實現隨手勢滑動控件
需求:
1.需要有單擊事件
2.可以隨手勢滑動
3.不會因父控件調用了 requestLayout()方法而回到初始位置
4.可以根據列表(ListView recyclerView)的滑動而隱藏,列表的停止而顯示。
實現隨手勢滑動
思路:重寫onTouchEvent(MotionEvent event) 方法,根據移動量,調用 void layout(int l, int t, int r, int b) 方法,重新設置位置即可。
private int lastX;
private int lastY;
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
lastX = (int) event.getRawX();//獲取觸摸事件觸摸位置的原始X座標
lastY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
//event.getRawX();獲得移動的位置
int dx = (int) event.getRawX() - lastX;
int dy = (int) event.getRawY() - lastY;
int l = this.getLeft() + dx;
int b = this.getBottom() + dy;
int r = getRight() + dx;
int t = getTop() + dy;
this.layout(l, t, r, b);//重新佈局
lastX = (int) event.getRawX();
lastY = (int) event.getRawY();
break;
case MotionEvent.ACTION_UP:
break;
}
return true;//由於要處理所有手勢,全部返回true;
}
問題:
1). 單擊事件不生效
2). 與其它可滑動View(如viewpager recyclerView) 存在滑動事件衝突
3). 該view可以滑出屏幕邊界
分析:
與其它View存在滑動事件衝突,可以在down事件中 調用如下代碼即可;
getParent().requestDisallowInterceptTouchEvent(true);//通知父控件不要攔截,自己處理手勢事件
單擊事件不生效是由於onTouchEvent 方法返回的全是true, 導致setOnclickListener 不能正常接收到點擊事件,如果onTouchEvent 方法返回的是super.onTouchEvent(event) ,那麼每次手勢事件,包括移動事件也會觸發單擊事件,這並不符合需求。爲了同時處理滑動事件和單擊事件,使用 GestureDetector 來處理複雜手勢,在 GestureDetector 的 onSingleTapUp 中處理單擊事件, 在onScroll方法中處理滑動事件。
View可滑出屏幕邊界的問題,對滑動事件中的位置做一些限制即可。
處理代碼如下:
package com.app.haotougu.views.widget;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import com.app.haotougu.common.utils.DensityUtils;
public class HandScollView extends View {
private static final String TAG = "HanderScollView";
private int lastX;
private int lastY;
private int mTranslationLenght;//位移長度
private ObjectAnimator mOutAnim;
private ObjectAnimator mInAnim;
private final int ANIM_DURATION = 300;
private GestureDetector mGestureDetector;
private OnClickListener mClickListener;
private boolean mAlreadyMove;//是否已經手動滑動
private int mScreenWidth;
private int mScreenHeight;
public HandScollView(Context context) {
super(context);
init(context);
}
public HandScollView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public HandScollView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
/**
* 單擊事件
*/
public void setOnSingleTapUp(@Nullable OnClickListener l) {
this.mClickListener = l;
}
private void init(Context context) {
//需要減掉圖片的高度
post(new Runnable() {
@Override
public void run() {
mTranslationLenght = getWidth();
mTranslationLenght += DensityUtils.dip2Intpx(context, 25);
}
});
DisplayMetrics dm = getResources().getDisplayMetrics();
mScreenWidth = dm.widthPixels;
mScreenHeight = dm.heightPixels;
mGestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
//當手指按下的時候觸發下面的方法
@Override
public boolean onDown(MotionEvent e) {
lastX = (int) e.getRawX();//獲取觸摸事件觸摸位置的原始X座標
lastY = (int) e.getRawY();
getParent().requestDisallowInterceptTouchEvent(true);
return true;
}
//當手指在屏幕上輕輕點擊的時候觸發下面的方法
@Override
public boolean onSingleTapUp(MotionEvent e) {
Log.w(TAG, "onSingleTapUp");
if (mClickListener != null) {
mClickListener.onClick(HandScollView.this);
}
return true;
}
//當手指在屏幕上滾動的時候觸發這個方法
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
dispathEvent(e2);
return true;
}
});
}
@Override
public void layout(int l, int t, int r, int b) {
if (!mAlreadyMove) {//防止父控件調用requestLayout()方法後,該view回到初始位置
super.layout(l, t, r, b);
}
}
private void moveLayout(int l, int t, int r, int b) {
super.layout(l, t, r, b);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mGestureDetector != null) {
mGestureDetector.onTouchEvent(event);
}
return true;
}
private void dispathEvent(MotionEvent event) {
int ea = event.getAction();
switch (ea) {
case MotionEvent.ACTION_MOVE:
//event.getRawX();獲得移動的位置
int dx = (int) event.getRawX() - lastX;
int dy = (int) event.getRawY() - lastY;
int l = this.getLeft() + dx;
int b = this.getBottom() + dy;
int r = getRight() + dx;
int t = getTop() + dy;
//下面判斷移動是否超出屏幕
if (l < 0) {
l = 0;
r = l + this.getWidth();
}
if (t < 0) {
t = 0;
b = t + this.getHeight();
}
if (r > mScreenWidth) {
r = mScreenWidth;
l = r - this.getWidth();
}
if (b > mScreenHeight) {
b = mScreenHeight;
t = b - this.getHeight();
}
if (!mAlreadyMove) {//判斷是否已經隨手勢滑動
if (Math.abs(dx) > 30 || Math.abs(dy) > 30) {
mAlreadyMove = true;
}
}
moveLayout(l, t, r, b);
// Log.e(TAG, "onTouch: " + l + "==" + t + "==" + r + "==" + b);
lastX = (int) event.getRawX();
lastY = (int) event.getRawY();
break;
case MotionEvent.ACTION_UP:
break;
}
}
/**
* 向右側位移,滑出屏幕
*/
public void hide() {
if (mOutAnim == null) {
mOutAnim = ObjectAnimator.ofFloat(this, "translationX", 0, mTranslationLenght);
}
if (mAlreadyMove || mOutAnim.isRunning()) {
return;
}
if ((mInAnim != null && mInAnim.isRunning())) {
mInAnim.end();
}
mOutAnim.setDuration(ANIM_DURATION);
mOutAnim.start();
}
/**
* 向左側位,由屏幕外向屏幕內移動
*/
public void show() {
if (mInAnim == null) {
mInAnim = ObjectAnimator.ofFloat(this, "translationX", mTranslationLenght, 0);
}
if (mAlreadyMove || mInAnim.isRunning()) {//如果已經手動滑動過,或正在執行動畫 則不再執行
return;
}
if (mOutAnim != null && mOutAnim.isRunning()) {
mOutAnim.end();
}
mInAnim.setDuration(ANIM_DURATION);
mInAnim.start();
}
}
單擊事件監聽 setOnSingleTapUp( OnClickListener l );
如代碼即可解決點擊事件無效,滑動事件衝突,滑出屏幕之外的問題。
備註:如上代碼關於隱藏與顯示動畫是根據項目需求而設定的,並不適用所有,如果有這方面的需求可自行更動畫。
效果圖如下: