自定義View,一是爲了滿足設計需求,二是開發者進階的標誌之一。隨心所欲就是我等奮鬥的目標!!!
效果
實現邏輯
-
明確需求
1、實現控件跟隨手指拖動
2、實現控件自動貼邊 -
整理思路
1、既然要實現控件拖動,那麼就離不開onTouchEvent()
這個方法,需要監聽裏面的按下和滑動事件。
2、 要實現自動貼邊,需要監聽onTouchEvent()
中手指離開屏幕事件。對於貼邊的過程,我們用屬性動畫來解決。
3、事件的衝突問題也需要考慮,拖動、點擊關係到了事件的攔截。 -
動手實現
在需求明確、思路清晰的情況下就要開始動手實現(需要了解自定義View的一些基礎API),下面代碼中註釋寫的基本都差不多,很好理解。歡迎指出討論!!!
完整代碼
/**
* 自定義View實現拖動並自動吸邊效果
* <p>
* 處理滑動和貼邊 {@link #onTouchEvent(MotionEvent)}
* 處理事件分發 {@link #dispatchTouchEvent(MotionEvent)}
* </p>
*
* @attr customIsAttach //是否需要自動吸邊
* @attr customIsDrag //是否可拖曳
*/
public class AttachButton extends View {
private float mLastRawX;
private float mLastRawY;
private final String TAG = "AttachButton";
private boolean isDrug = false;
private int mRootMeasuredWidth = 0;
private int mRootMeasuredHeight = 0;
private int mRootTopY = 0;
private boolean customIsAttach;
private boolean customIsDrag;
public AttachButton(Context context) {
this(context, null);
}
public AttachButton(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public AttachButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(context, attrs);
}
/**
* 初始化自定義屬性
*/
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray mTypedAttay = context.obtainStyledAttributes(attrs, R.styleable.AttachButton);
customIsAttach = mTypedAttay.getBoolean(R.styleable.AttachButton_customIsAttach, true);
customIsDrag = mTypedAttay.getBoolean(R.styleable.AttachButton_customIsDrag, true);
mTypedAttay.recycle();
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
super.dispatchTouchEvent(event);
return true;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
//判斷是否需要滑動
if (customIsDrag) {
//當前手指的座標
float mRawX = ev.getRawX();
float mRawY = ev.getRawY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN://手指按下
isDrug = false;
//記錄按下的位置
mLastRawX = mRawX;
mLastRawY = mRawY;
ViewGroup mViewGroup = (ViewGroup) getParent();
if (mViewGroup != null) {
int[] location = new int[2];
mViewGroup.getLocationInWindow(location);
//獲取父佈局的高度
mRootMeasuredHeight = mViewGroup.getMeasuredHeight();
mRootMeasuredWidth = mViewGroup.getMeasuredWidth();
//獲取父佈局頂點的座標
mRootTopY = location[1];
}
break;
case MotionEvent.ACTION_MOVE://手指滑動
if (mRawX >= 0 && mRawX <= mRootMeasuredWidth && mRawY >= mRootTopY && mRawY <= (mRootMeasuredHeight + mRootTopY)) {
//手指X軸滑動距離
float differenceValueX = mRawX - mLastRawX;
//手指Y軸滑動距離
float differenceValueY = mRawY - mLastRawY;
//判斷是否爲拖動操作
if (!isDrug) {
if (Math.sqrt(differenceValueX * differenceValueX + differenceValueY * differenceValueY) < 2) {
isDrug = false;
} else {
isDrug = true;
}
}
//獲取手指按下的距離與控件本身X軸的距離
float ownX = getX();
//獲取手指按下的距離與控件本身Y軸的距離
float ownY = getY();
//理論中X軸拖動的距離
float endX = ownX + differenceValueX;
//理論中Y軸拖動的距離
float endY = ownY + differenceValueY;
//X軸可以拖動的最大距離
float maxX = mRootMeasuredWidth - getWidth();
//Y軸可以拖動的最大距離
float maxY = mRootMeasuredHeight - getHeight();
//X軸邊界限制
endX = endX < 0 ? 0 : endX > maxX ? maxX : endX;
//Y軸邊界限制
endY = endY < 0 ? 0 : endY > maxY ? maxY : endY;
//開始移動
setX(endX);
setY(endY);
//記錄位置
mLastRawX = mRawX;
mLastRawY = mRawY;
}
break;
case MotionEvent.ACTION_UP://手指離開
//根據自定義屬性判斷是否需要貼邊
if (customIsAttach) {
//判斷是否爲點擊事件
if (isDrug) {
float center = mRootMeasuredWidth / 2;
//自動貼邊
if (mLastRawX <= center) {
//向左貼邊
AttachButton.this.animate()
.setInterpolator(new BounceInterpolator())
.setDuration(500)
.x(0)
.start();
} else {
//向右貼邊
AttachButton.this.animate()
.setInterpolator(new BounceInterpolator())
.setDuration(500)
.x(mRootMeasuredWidth - getWidth())
.start();
}
}
}
break;
}
}
//是否攔截事件
return isDrug ? isDrug : super.onTouchEvent(ev);
}
}
- 自定義屬性
<declare-styleable name="AttachButton">
<!--是否需要自動吸邊-->
<attr name="customIsAttach" format="boolean" />
<!--是否可拖曳-->
<attr name="customIsDrag" format="boolean" />
</declare-styleable>