android自定义可拖动的悬浮按钮

在页面上随意拖动的按钮

public class MoveScaleRotateView extends RelativeLayout {
    private Context mContext;

    //默认的触摸点ID
    private static final int INVALID_POINTER_ID = -1;
    //子View上的两个手指的触摸点ID
    private int mChildPtrID1 = INVALID_POINTER_ID, mChildPtrID2
            = INVALID_POINTER_ID;
    //父View上的两个手指的触摸点ID
    private int mPtrID1 = INVALID_POINTER_ID, mPtrID2 = INVALID_POINTER_ID;

    //父布局的Event事件
    private MotionEvent mEvent;

    //记录点击在子View上的x和y坐标
    private float mChildActionDownX = 0;
    private float mChildActionDownY = 0;

    //记录点击在父View上的第一个点和第二个点的x和y坐标
    private float mActionDownX1 = 0;
    private float mActionDownX2 = 0;
    private float mActionDownY1 = 0;
    private float mActionDownY2 = 0;

    //初始的旋转角度
    private float mDefaultAngle;
    //当前旋转角度
    private float mAngle;

    //记录原始落点的时候两个手指之间的距离
    private float oldDist = 0;
    //是否是点击
    private boolean isCheck=false;

    //测试View
    private View view;
    private DisplayMetrics dm;

    //初始化操作
    private void init(final Context context) {
        mContext = context;
        view = View.inflate(context, R.layout.layout_test_rela_view, null);
        addView(view);
        dm = new DisplayMetrics();
        dm = getResources().getDisplayMetrics();
        view.setX(dm.widthPixels-80);
        view.setY(dm.heightPixels/2);
        this.setFocusableInTouchMode(true);

        view.setOnTouchListener(new OnTouchListener() {

            private float lastY;
            private float lastX;
            private float rawY;
            private float rawX;

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch ( event.getAction() & MotionEvent.ACTION_MASK ) {
                    case MotionEvent.ACTION_DOWN:
                        mChildPtrID1 = event.getPointerId(event.getActionIndex());
                        if ( mEvent != null ) {
                            mChildActionDownX = mEvent.getX(event.findPointerIndex(mChildPtrID1))
                                    - view.getX();
                            mChildActionDownY = mEvent.getY(event.findPointerIndex(mChildPtrID1))
                                    - view.getY();
                            rawX = (event.getRawX());
                            rawY = (event.getRawY());
                            lastX = rawX;
                            lastY = rawY;
                        }else {
                            return false;
                        }
                        break;
                    case MotionEvent.ACTION_MOVE:
                        if ( mEvent != null ) {
//                            try {
                                float x1 = mEvent.getX(mEvent.findPointerIndex(mChildPtrID1));
                                float y1 = mEvent.getY(mEvent.findPointerIndex(mChildPtrID1));
                            if (x1 - mChildActionDownX<=0){
                                view.setX(0);
                            }else if (x1 - mChildActionDownX>=dm.widthPixels-30){
                                view.setX( dm.widthPixels-30);
                            }else{
                                view.setX(x1 - mChildActionDownX);
                            }


                            if (y1 - mChildActionDownY<=0){
                                view.setY(0);
                            }else if (y1 - mChildActionDownY>=dm.heightPixels-30){
                                view.setY( dm.heightPixels-30);
                            }else{
                                view.setY(y1 - mChildActionDownY);
                            }
                            lastX = (event.getRawX());
                            lastY = (event.getRawY());
                            Log.v("sadadas",lastX+"--"+lastY+"--"+x1+"--"+y1);
//                            }catch (Exception e){}
                        }else {
                            return false;
                        }

                        break;
                    case MotionEvent.ACTION_UP:
                        mChildPtrID1 = INVALID_POINTER_ID;
                        if (rawX-lastX<=10&&rawX-lastX>=-10
                                &&rawY-lastY>=-10&&rawY-lastY<=10){

                        }

                        break;

                    case MotionEvent.ACTION_CANCEL:
                        mChildPtrID1 = INVALID_POINTER_ID;
                        mChildPtrID2 = INVALID_POINTER_ID;
                        break;
                }
                return true;
            }
        });
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mEvent = event;
        switch ( event.getAction() & MotionEvent.ACTION_MASK ) {
            case MotionEvent.ACTION_DOWN:
                mPtrID1 = event.getPointerId(event.getActionIndex());
                mActionDownX1 = event.getX(event.findPointerIndex(mPtrID1));
                mActionDownY1 = event.getY(event.findPointerIndex(mPtrID1));
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                //非第一个触摸点按下
                mPtrID2 = event.getPointerId(event.getActionIndex());
                mActionDownX2 = event.getX(event.findPointerIndex(mPtrID2));
                mActionDownY2 = event.getY(event.findPointerIndex(mPtrID2));

                oldDist = spacing(event, mPtrID1, mPtrID2);
                break;
            case MotionEvent.ACTION_MOVE:
                if ( mPtrID1 != INVALID_POINTER_ID && mPtrID2 != INVALID_POINTER_ID ) {
                    float x1 = 0, x2 = 0, y1 = 0, y2 = 0;
                    x1 = event.getX(event.findPointerIndex(mPtrID1));
                    y1 = event.getY(event.findPointerIndex(mPtrID1));
                    x2 = event.getX(event.findPointerIndex(mPtrID2));
                    y2 = event.getY(event.findPointerIndex(mPtrID2));

                    //在这里处理旋转逻辑
                    mAngle = angleBetweenLines(mActionDownX1, mActionDownY1, mActionDownX2,
                            mActionDownY2, x1, y1, x2, y2) + mDefaultAngle;
                    view.setRotation(mAngle);

                    //在这里处理缩放的逻辑
                    //处理缩放模块
                    float newDist = spacing(event, mPtrID1, mPtrID2);
                    float scale = newDist / oldDist;
                    if ( newDist > oldDist + 1 ) {
                        zoom(scale, view);
                        oldDist = newDist;
                    }
                    if ( newDist < oldDist - 1 ) {
                        zoom(scale, view);
                        oldDist = newDist;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                mPtrID1 = INVALID_POINTER_ID;
                break;
            case MotionEvent.ACTION_POINTER_UP:
                //非第一个触摸点抬起
                mPtrID2 = INVALID_POINTER_ID;
                mDefaultAngle = mAngle;
                break;
            case MotionEvent.ACTION_CANCEL:
                mPtrID1 = INVALID_POINTER_ID;
                mPtrID2 = INVALID_POINTER_ID;
                break;
        }
        return false;
    }

    //对控件进行缩放操作
    private void zoom(float scale, View view) {
        int w = view.getWidth();
        int h = view.getHeight();
        view.setLayoutParams(new RelativeLayout.LayoutParams(( int ) (w * scale), ( int ) (h * scale)));
    }

    /**
     * 计算两点之间的距离
     *
     * @param event
     * @return 两点之间的距离
     */
    private float spacing(MotionEvent event, int ID1, int ID2) {
        float x = event.getX(ID1) - event.getX(ID2);
        float y = event.getY(ID1) - event.getY(ID2);
        return 1;
    }

    /**
     * 计算刚开始触摸的两个点构成的直线和滑动过程中两个点构成直线的角度
     *
     * @param fX  初始点一号x坐标
     * @param fY  初始点一号y坐标
     * @param sX  初始点二号x坐标
     * @param sY  初始点二号y坐标
     * @param nfX 终点一号x坐标
     * @param nfY 终点一号y坐标
     * @param nsX 终点二号x坐标
     * @param nsY 终点二号y坐标
     * @return 构成的角度值
     */
    private float angleBetweenLines(float fX, float fY, float sX, float sY, float nfX, float nfY, float nsX, float nsY) {
        float angle1 = ( float ) Math.atan2((fY - sY), (fX - sX));
        float angle2 = ( float ) Math.atan2((nfY - nsY), (nfX - nsX));

        float angle = (( float ) Math.toDegrees(angle1 - angle2)) % 360;
        if ( angle < -180.f ) angle += 360.0f;
        if ( angle > 180.f ) angle -= 360.0f;
        return -angle;
    }


    public MoveScaleRotateView(Context context) {
        super(context);
        init(context);
    }

    public MoveScaleRotateView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public MoveScaleRotateView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    @TargetApi( Build.VERSION_CODES.LOLLIPOP )
    public MoveScaleRotateView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context);
    }

    /**
     * 测试用 显示Toast
     *
     * @param msg
     */
    private void showToast(String msg) {
        Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
    }

    /**
     * 测试用 打印log
     *
     * @param log
     */
    private void log(String log) {
        Log.e("HHHHHHHHHH", log);
    }

    /**
     * 测试用 打印log 指定TAG
     *
     * @param log
     * @param tag
     */
    private void log(String log, String tag) {
        Log.e(tag, log);
    }


}
按钮布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="70dp"
    android:layout_height="70dp"
    android:focusableInTouchMode="true">



    <TextView
        android:text="取件"
        android:focusableInTouchMode="true"
        android:layout_centerInParent="true"
        android:background="@drawable/shape_yellow"
        android:gravity="center"
        android:textColor="#ffffff"
        android:textSize="15sp"
        android:layout_width="50dp"
        android:layout_height="50dp" />

</RelativeLayout>
按钮背景设置:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">

    <corners android:radius="360dp"></corners>
    <solid android:color="#FCDB12"></solid>

</shape>

效果图:





两个问题注意点:

1.以上的代码,按钮时可以旋转的,如果不需要注释掉,一下这行代码即可:

  view.setRotation(mAngle);

2.第一次进入带有此按钮的页面,点击悬浮按钮,响应悬浮按钮下面的事件:

  原因:父类mEvent为空,悬浮按钮的触摸事件,返回false,造成事件穿透。

  解决方法:在悬浮按钮的Activity中做一个事件分发:

 

 /**
     * 事件分发
     * @param ev
     * @return
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        iv_pickup.onTouchEvent(ev);//悬浮按钮
        return super.dispatchTouchEvent(ev);
    }

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 19
    评论
Android全局拖动悬浮按钮是一种在应用界面上悬浮按钮,用户可以通过拖动按钮的方式,在任意界面使用其提供的功能。这种悬浮按钮通常会呈现在屏幕的某个固定位置,如屏幕右下角。 使用全局拖动悬浮按钮可以为用户提供方便和快捷的操作方式。用户只需通过轻触按钮即可快速打开某个应用或执行某个功能,而不需要返回主界面或搜索相应的功能选项。这样可以大大提高用户的操作效率和体验。 开发这一功能的关键在于如何实现悬浮按钮在全局范围内的拖动和点击事件。一种常用的方法是通过在Android系统的WindowManager中创建一个可拖动的View,并设置其触摸事件监听器来实现。在触摸事件监听器中,我们可以处理按钮拖动、点击、长按等各种事件。 为了使全局拖动悬浮按钮更好地融入应用界面,我们可以对其进行自定义设置。例如,可以自定义按钮的形状、颜色、动画效果等,以适配不同的应用主题和风格。 需要注意的是,在设计和使用全局拖动悬浮按钮时,我们要遵循用户界面设计的基本原则,避免对用户的正常操作造成干扰和困扰。同时,也要考虑到移动设备的屏幕尺寸和分辨率的差异,以确保全局拖动悬浮按钮在不同设备上都能够正常显示和操作。 总而言之,Android全局拖动悬浮按钮是一种方便用户操作的功能,通过简单轻松的拖动和点击,用户可以快速访问应用的各种功能。开发者可以根据应用的需求和用户体验考虑,来设计和实现这一功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值