android AttachLayout 拖動吸邊效果


package com.xin.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.view.animation.BounceInterpolator;
import android.widget.LinearLayout;

import androidx.annotation.Nullable;

import com.hrg.hefei.R;

/**
 * 自定義View實現(xiàn)拖動并自動吸邊效果
 * <p>
 * 處理滑動和貼邊 {@link #onTouchEvent(MotionEvent)}
 * 處理事件分發(fā) {@link #dispatchTouchEvent(MotionEvent)}
 * </p>
 *
 * @attr customIsAttach  //是否需要自動吸邊
 * @attr customIsDrag    //是否可拖曳
 */
public class AttachLayout extends LinearLayout {
    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 int customAttachDirect; /*-1不吸附 0所有的邊 1左 2上 3右 4下 5左右 6上下 */
    private boolean customIsDrag;
    private boolean touchIsTargetView = true;
    private int customIsTargetView;
    private View targetView;

    public AttachLayout(Context context) {
        this(context, null);
    }

    public AttachLayout(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AttachLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttrs(context, attrs);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (customIsTargetView > 0) {
            targetView = findViewById(customIsTargetView);
        }
    }

    /**
     * 初始化自定義屬性
     */
    private void initAttrs(Context context, AttributeSet attrs) {
        TypedArray mTypedAttay = context.obtainStyledAttributes(attrs, R.styleable.AttachLayout);
        customAttachDirect = mTypedAttay.getInteger(R.styleable.AttachLayout_customAttachDirect, 0);
        customIsDrag = mTypedAttay.getBoolean(R.styleable.AttachLayout_customIsDrag, true);
        customIsTargetView = mTypedAttay.getResourceId(R.styleable.AttachLayout_customIsTargetView, 0);
        mTypedAttay.recycle();
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                touchIsTargetView = true;
        }
        if (targetView != null) {
            // /*判斷是否有目標(biāo)view,若有不在目標(biāo)view內(nèi)不執(zhí)行*/
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN://手指按下
                    Rect rect = new Rect();
                    targetView.getHitRect(rect);
                    if (!rect.contains((int) event.getX(), (int) event.getY())) {
                        touchIsTargetView = false;
                    }
            }
        }
        if (touchIsTargetView) {
            doTouch(event);
        }
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        //判斷是否需要滑動
//        doTouch(ev); /*不能放在這公般,放在這子view收不到事件了*/
        //是否攔截事件
        if (isDrug) {
            return true;
        }
        return super.onTouchEvent(ev);
    }

    private void doTouch(MotionEvent ev) {
        if (customIsDrag) {
            //當(dāng)前手指的坐標(biāo)
            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();
                        //獲取父布局頂點的坐標(biāo)
                        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://手指離開
                    //根據(jù)自定義屬性判斷是否需要貼邊
                    if (customAttachDirect >= 0 && isDrug) {
                        //判斷是否為點擊事件 0所有的邊 1左 2上 3右 4下 5左右 6上下
                        float centerX = mRootMeasuredWidth / 2;
                        float centerY = mRootMeasuredHeight / 2;
                        float x = -1, y = -1;
                        if (customAttachDirect == 1) { /*左*/
                            x = 0;
                            y = -1;
                        } else if (customAttachDirect == 2) { /*上*/
                            x = -1;
                            y = 0;
                        } else if (customAttachDirect == 3) { /*右*/
                            x = mRootMeasuredWidth - getWidth();
                            y = -1;
                        } else if (customAttachDirect == 4) {
                            x = -1;
                            y = mRootMeasuredWidth - getHeight();
                        } else if (customAttachDirect == 5) { /*左右*/
                            x = mLastRawX <= centerX ? 0 : (mRootMeasuredWidth - getWidth());
                            y = -1;
                        } else if (customAttachDirect == 6) { /*上下*/
                            x = -1;
                            y = mLastRawY <= centerY ? 0 : (mRootMeasuredHeight - getHeight());
                        } else if (customAttachDirect == 0) { /*距那個邊近去那個*/
                            // TODO: 2020/5/28 0028
                        }
                        //自動貼邊
                        ViewPropertyAnimator animator = AttachLayout.this.animate()
                                .setInterpolator(new BounceInterpolator())
                                .setDuration(500);
                        if (x >= 0) {
                            animator.x(x);
                        }
                        if (y >= 0) {
                            animator.y(y);
                        }
                        animator.start();
                    }
                    break;
            }
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市凉敲,隨后出現(xiàn)的幾起案子茎刚,更是在濱河造成了極大的恐慌业栅,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異憨愉,居然都是意外死亡兼吓,警方通過查閱死者的電腦和手機臂港,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人审孽,你說我怎么就攤上這事县袱。” “怎么了佑力?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵式散,是天一觀的道長。 經(jīng)常有香客問我打颤,道長暴拄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任瘸洛,我火速辦了婚禮揍移,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘反肋。我一直安慰自己那伐,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布石蔗。 她就那樣靜靜地躺著罕邀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪养距。 梳的紋絲不亂的頭發(fā)上诉探,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天,我揣著相機與錄音棍厌,去河邊找鬼肾胯。 笑死,一個胖子當(dāng)著我的面吹牛耘纱,可吹牛的內(nèi)容都是我干的敬肚。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼束析,長吁一口氣:“原來是場噩夢啊……” “哼艳馒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起员寇,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤弄慰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蝶锋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體陆爽,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年扳缕,在試婚紗的時候發(fā)現(xiàn)自己被綠了墓陈。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恶守。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖贡必,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情庸毫,我是刑警寧澤仔拟,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站飒赃,受9級特大地震影響利花,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜载佳,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一炒事、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蔫慧,春花似錦挠乳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至黍析,卻和暖如春卖怜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背阐枣。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工马靠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蔼两。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓甩鳄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親宪哩。 傳聞我的和親對象是個殘疾皇子娩贷,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,515評論 2 359