自定义view 拖拽、自吸边控件

这是一个自定义的AndroidFrameLayout,支持拖动和自动吸边效果。代码中包含了拖动检测、边界限制以及放大缩小动画,同时提供了自定义属性以控制是否开启拖动和吸边功能。
摘要由CSDN通过智能技术生成

先看效果

 直接上代码


/**
 * @Author : 马占柱
 * E-mail : mazhanzhu_3351@163.com
 * Time   : 2023/6/17 23:18
 * Desc   : 拖拽、自吸边控件
 */
public class MoveFrameLayout extends FrameLayout {

    private boolean isScaled;//是否已经放大
    private AnimatorSet mAnimatorSet;//动画集合

    public MoveFrameLayout(@NonNull Context context) {
        this(context, null);
    }

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

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

    private boolean isAttach;//是否自动吸边
    private boolean isDrag;//是否可拖动

    /**
     * 初始化自定义属性
     */
    private void initAttrs(Context context, AttributeSet attrs) {
        TypedArray mTypedAttay = context.obtainStyledAttributes(attrs, R.styleable.MoveFrameLayout);
        isAttach = mTypedAttay.getBoolean(R.styleable.MoveFrameLayout_IsAttach, true);
        isDrag = mTypedAttay.getBoolean(R.styleable.MoveFrameLayout_IsDrag, true);
        mTypedAttay.recycle();
    }

    private int mParentWidth = 0;//父控件的宽
    private int mParentHeight = 0;//父控件的高

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //获取父控件宽高
        ViewGroup mViewGroup = (ViewGroup) getParent();
        if (mViewGroup != null) {
            int[] location = new int[2];
            mViewGroup.getLocationOnScreen(location);
            //获取父布局的高度
            mParentHeight = mViewGroup.getMeasuredHeight();
            mParentWidth = mViewGroup.getMeasuredWidth();
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //点击点在控件区域内,接受事件
        boolean inRangeOfView = inRangeOfView(this, ev);
        return inRangeOfView;
    }

    private float mLastX;//按下位置x
    private float mLastY;//按下位置Y
    private boolean isDrug = false;//是否是拖动

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (isDrag) {
            //请求父控件将事件交由自己处理
            getParent().requestDisallowInterceptTouchEvent(true);
            float mX = ev.getX();
            float mY = ev.getY();
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    //重置拖动状态
                    isDrug = false;
                    //记录按下的位置
                    mLastX = mX;
                    mLastY = mY;
                    isScaled = false;
                    break;
                case MotionEvent.ACTION_MOVE:
                    //手指X轴滑动距离
                    float differenceValueX = mX - mLastX;
                    //手指Y轴滑动距离
                    float differenceValueY = mY - mLastY;
                    //判断是否为拖动操作
                    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 = mParentWidth - getWidth();
                    //Y轴可以拖动的最大距离
                    float maxY = mParentHeight -PxUtils.dp2px(50);//首页下面是50的tab高度
                    //X轴边界限制---这里对X轴拖动不做限制
                    //endX = endX < 0 ? 0 : endX > maxX ? maxX : endX;
                    //Y轴边界限制
                    endY = endY < 0 ? 0 : endY > maxY ? maxY : endY;
                    //开始移动
                    setX(endX);
                    setY(endY);
                    if (isDrug && !isScaled) {
                        //放大动画
                        startScaleAnimator(1.0f, 1.5f, 300);
                        //Animator scaleAnimator = AnimatorInflater.loadAnimator(getContext(), R.animator.anim_scale_up);
                        //scaleAnimator.setTarget(this);
                        //scaleAnimator.start();
                        isScaled = true;
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    //根据自定义属性判断是否需要贴边
                    if (isAttach) {
                        //判断是否为点击事件
                        float center = mParentWidth / 2;
                        //自动贴边
                        if (ev.getRawX() <= center) {
                            //向左贴边
                            animate()
                                    //.setInterpolator(new BounceInterpolator())
                                    .setInterpolator(new OvershootInterpolator())
                                    .setDuration(400)
                                    .x(PxUtils.dp2px(0))
                                    .start();
                        } else {
                            //向右贴边
                            animate()
                                    .setInterpolator(new OvershootInterpolator())
                                    .setDuration(400)
                                    .x(mParentWidth - getWidth() - PxUtils.dp2px(0))
                                    .start();
                        }
                        if (isScaled) {
                            //缩小动画
                            startScaleAnimator(1.5f, 1.0f, 300);
                            //Animator scaleAnimator = AnimatorInflater.loadAnimator(getContext(), R.animator.anim_scale_down);
                            //scaleAnimator.setTarget(this);
                            //scaleAnimator.start();
                            isScaled = false;
                        }
                    }
                    break;
            }
        }
        return isDrug ? true : super.onTouchEvent(ev);
    }

    /**
     * 点击点是否在控件区域
     *
     * @param view
     * @param ev
     * @return
     */
    private boolean inRangeOfView(View view, MotionEvent ev) {
        Rect rect = new Rect();
        view.getGlobalVisibleRect(rect);
        int rawX = (int) ev.getRawX();
        int rawY = (int) ev.getRawY();
        if (rect.contains(rawX, rawY)) {
            return true;
        }
        return false;
    }

    /**
     * 执行动画
     *
     * @param valueFrom
     * @param valueTo
     * @param duration
     */
    private void startScaleAnimator(float valueFrom, float valueTo, int duration) {
        ObjectAnimator scaleX = ObjectAnimator.ofFloat(this, "scaleX", valueFrom, valueTo);
        ObjectAnimator scaleY = ObjectAnimator.ofFloat(this, "scaleY", valueFrom, valueTo);
        if (null == mAnimatorSet) {
            mAnimatorSet = new AnimatorSet();
        }
        mAnimatorSet.play(scaleX).with(scaleY);
        mAnimatorSet.setDuration(duration);
        mAnimatorSet.start();
    }
}

然后在布局里面使用

<com.ouhui.store.view.MoveFrameLayout
        android:id="@+id/me_kefu"
        android:layout_width="@dimen/dp_50"
        android:layout_height="@dimen/dp_50"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="@dimen/dp_50"
        app:IsAttach="true"
        app:IsDrag="true">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@drawable/bg_orange_100">

            <ImageView
                android:layout_centerHorizontal="true"
                android:layout_width="@dimen/dp_20"
                android:layout_height="@dimen/dp_20"
                android:layout_gravity="center_horizontal"
                android:layout_marginTop="@dimen/dp_5"
                android:background="@mipmap/ic_me_kefu" />

            <TextView
                android:layout_centerHorizontal="true"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_marginTop="@dimen/dp_25"
                android:text="客服"
                android:textColor="@color/white"
                android:textSize="@dimen/sp_10" />
        </RelativeLayout>
    </com.ouhui.store.view.MoveFrameLayout>

别忘了在values的attrs.xml里面添加自定义属性

    <declare-styleable name="MoveFrameLayout">
        <!--是否需要自动吸边-->
        <attr name="IsAttach" format="boolean" />
        <!--是否可拖曳-->
        <attr name="IsDrag" format="boolean" />
    </declare-styleable>

代码里面有一个工具类,可能有的人不清楚,我也发出来吧


/**
 * Time   : 2021/6/17 10:27
 * Desc   : 尺寸工具类
 */
public class PxUtils {
    public static final String TAG = "PxUtils";

    /**
     * 得到设备屏幕的宽度
     */
    public static int getScreenWidth(Context context) {
        return context.getResources().getDisplayMetrics().widthPixels;
    }

    /**
     * 得到设备屏幕的高度
     */
    public static int getScreenHeight(Context context) {
        return context.getResources().getDisplayMetrics().heightPixels;
    }

    /**
     * 得到设备的密度
     */
    public static float getScreenDensity(Context context) {
        return context.getResources().getDisplayMetrics().density;
    }

    /**
     * 把密度【dp】转换为像素【px】
     */
    public static int dp2px(float dipValue) {
        final float scale = getScreenDensity(BaseApplication.getContext());
        return (int) (dipValue * scale + 0.5);
    }

    /**
     * 将sp值转换为px值,保证文字大小不变
     */
    public static int sp2px(float spValue) {
        final float fontScale = BaseApplication.getContext().getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }

    /**
     * 下面都是工具类,dp单位转px单位
     */
    public static int dp2px(Context c, float dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, c.getResources().getDisplayMetrics());
    }

    public static float dp2pxF(Context c, float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, c.getResources().getDisplayMetrics());
    }

    public static float sp2pxF(Context c, float sp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, c.getResources().getDisplayMetrics());
    }

    public float caculate(ArrayList<PointXY> list, Context context) {
        float temp = 0;
        for (int i = 0; i < list.size(); i++) {
            if (i < list.size() - 1) {
                PointXY p1 = list.get(i);
                PointXY p2 = list.get(i + 1);
                temp += p1.getX() * p2.getY() - p2.getX() * p1.getY();
            } else {
                PointXY pn = list.get(i);
                PointXY p0 = list.get(0);
                temp += pn.getX() * p0.getY() - p0.getX() * pn.getY();
            }
        }
        temp = temp / 2;
        ArrayList<Float> area = getScreenSizeOfDevice(context);
        temp = (temp / area.get(0)) * area.get(1) * 2.54f;
        Log_Ma.e(TAG, "画图的面积 : " + temp);
        return temp;
    }

    private ArrayList<Float> getScreenSizeOfDevice(Context context) {
        ArrayList<Float> list02 = new ArrayList<Float>();
        Point point = new Point();
        DisplayMetrics dm = context.getResources().getDisplayMetrics();
        float area = point.x * point.y;//得到像素面积,
        Log_Ma.d(TAG, "屏幕的总像素面积: " + area);
        list02.clear();
        list02.add(area);
        double x = Math.pow(point.x / dm.xdpi, 2);
        double y = Math.pow(point.y / dm.ydpi, 2);
        double screenInches = Math.sqrt(x + y);//得到尺寸大小
        list02.add((float) (x * y));//得到手机屏幕的物理面积
        // Log.d(TAG, "屏幕尺寸大小: "+screenInches);
        return list02;
    }

    public class PointXY {
        public float X;
        public float Y;

        public PointXY() {
            this.X = 0;
            this.Y = 0;
        }

        public PointXY(float x, float y) {
            X = x;
            Y = y;
        }

        public float getX() {
            return X;
        }

        public void setX(float x) {
            X = x;
        }

        public float getY() {
            return Y;
        }

        public void setY(float y) {
            Y = y;
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值