1. 程式人生 > >android自定義可拖動的懸浮按鈕

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);
    }