今天來寫個可拖動的按鈕吧

在學習Android的過程中,一個按鈕、一個文字示圖可以說是基本到不能再基本的東西了

不過在某些APP中,有些按鈕或圖片等等都是可以拖曳的(-‿◦)

..阿咧?不太懂我在說什麼嗎?好吧,直接看範例

 

 

今天要做的大概就是這麼一個東西~

大概理解了嗎?那麼我們開始吧:D

喔對了!

這次沒有Github喔!需要程式碼的在底下留Mail我會給你

 


 

1. 原理

 

首先整體來說,原理就是跟寫一個自定義元件是一樣的道理

可以參照一下我之前寫的

->碼農日常-『Android studio』簡單地教你實現在Android畫一個弧形儀表盤

 

但是這次的沒有那麼複雜,這次我們要直接搬Android之中的原生按鈕來做魔改

那麼,先來看一下這次的檔案內容

截圖 2021-04-10 下午6.09.38

 

而因為這次相當於要寫一個新的自定義View, 所以介面就娜到最後囉

 


 

2. 程式內容

 

直接看一下程式內容

 

public class DraggableButton extends androidx.appcompat.widget.AppCompatButton {

    float lastX = 0f, lastY = 0f;
    private float beginX = 0f, beginY = 0f;
    int screenWidth = 720, screenHeight = 1280;


    public DraggableButton(@NonNull Context context) {
        super(context);
    }

    public DraggableButton(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

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

    @SuppressLint("DrawAllocation")
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        assert wm != null;
        wm.getDefaultDisplay().getMetrics(dm);
        /**取得螢幕的總寬跟總高*/
        screenHeight = dm.heightPixels-200;
        screenWidth = dm.widthPixels;
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:
                /**當按鈕被按下*/
                //取得目前位置
                lastX = event.getRawX();
                lastY = event.getRawY();
                //將位置寫為起始位置
                beginX = lastX;
                beginY = lastY;

                break;

            case MotionEvent.ACTION_MOVE:
                //取得觸摸點相對於螢幕的座標
                float dx = event.getRawX() - lastX;
                float dy = event.getRawY() - lastY;
                float left, top, right, bottom;
                left = getLeft() + dx;
                top = getTop() + dy;
                right = getRight() + dx;
                bottom = getBottom() + dy;
                //以下判斷為避免物件被拉出畫面
                if(left < 0){
                    left = 0;
                    right = left + getWidth();
                }
                if(right > screenWidth){
                    right = screenWidth;
                    left = right - getWidth();
                }
                if(top < 0){
                    top = 0;
                    bottom = top + getHeight();
                }
                if(bottom>screenHeight){
                    bottom = screenHeight;
                    top = bottom - getHeight();
                }
                //設置被拉到的位置
                layout(Math.round(left),Math.round(top),Math.round(right),Math.round(bottom));
                lastY = event.getRawY();
                lastX = event.getRawX();
                break;

            case MotionEvent.ACTION_UP:
                //如果移動距離小於10,則被視為點擊按鈕;反之則為拖曳按鈕
                if (Math.abs(lastX - beginX)< 10 && Math.abs(lastY - beginY)< 10){
                    return super.onTouchEvent(event);
                }else{
                    setPressed(false);
                    return true;
                }
        }

        return super.onTouchEvent(event);
    }
}

 

首先,開頭的那個public一定記得要加

不然後面東西跑不出來不要怪我(´・д・`)

 

重點稍微講一下

首先,我們必須先知道螢幕整體大小

而該程式就寫在onMeasure

@SuppressLint("DrawAllocation")
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
    DisplayMetrics dm = new DisplayMetrics();
    assert wm != null;
    wm.getDefaultDisplay().getMetrics(dm);
    /**取得螢幕的總寬跟總高*/
    screenHeight = dm.heightPixels-200;
    screenWidth = dm.widthPixels;
}

 

再來,請要讓這個View可以被拖動

而相關程式就是onTouchEvent的部分

 

首先,請讓程式複寫onTouchEvent

截圖 2021-04-10 下午6.19.21

 

然後,讓他攔截

按下(MotionEvent.ACTION_DOWN)

拖曳中(MotionEvent.ACTION_MOVE)

放開(MotionEvent.ACTION_UP)

三個事件

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {

        case MotionEvent.ACTION_DOWN:
       
            break;

        case MotionEvent.ACTION_MOVE:
            
            break;

        case MotionEvent.ACTION_UP:
            
            
    }
    return super.onTouchEvent(event);
}

 

然後,在MotionEvent.ACTION_DOWN寫入

case MotionEvent.ACTION_DOWN:
    /**當按鈕被按下*/
    //取得目前位置
    lastX = event.getRawX();
    lastY = event.getRawY();
    //將位置寫為起始位置
    beginX = lastX;
    beginY = lastY;

    break;

 

在MotionEvent.ACTION_MOVE寫入

case MotionEvent.ACTION_MOVE:
    //取得觸摸點相對於螢幕的座標
    float dx = event.getRawX() - lastX;
    float dy = event.getRawY() - lastY;
    float left, top, right, bottom;
    left = getLeft() + dx;
    top = getTop() + dy;
    right = getRight() + dx;
    bottom = getBottom() + dy;
    //以下判斷為避免物件被拉出畫面
    if(left < 0){
        left = 0;
        right = left + getWidth();
    }
    if(right > screenWidth){
        right = screenWidth;
        left = right - getWidth();
    }
    if(top < 0){
        top = 0;
        bottom = top + getHeight();
    }
    if(bottom>screenHeight){
        bottom = screenHeight;
        top = bottom - getHeight();
    }
    //設置被拉到的位置
    layout(Math.round(left),Math.round(top),Math.round(right),Math.round(bottom));
    lastY = event.getRawY();
    lastX = event.getRawX();
    break;

在MotionEvent.ACTION_UP寫入

case MotionEvent.ACTION_UP:
    //如果移動距離小於10,則被視為點擊按鈕;反之則為拖曳按鈕
    if (Math.abs(lastX - beginX)< 10 && Math.abs(lastY - beginY)< 10){
        return super.onTouchEvent(event);
    }else{
        setPressed(false);
        return true;
    }

 

基本上,重點是在最後的那個放開事件

因為拖曳本質上也是先要按下按鈕

所以在放開的部分,便是先偵測使用者究竟是拖曳還是點擊,如果是點擊的話則設定按鈕"被按下",

反之則不回傳任何事件

 


 

3. 介面

 

最後就是介面了

界面超級簡單,基本上跟放一個Button完全一樣

BTW,由於本元件繼承Button撰寫,因此所有的功能都跟Button一樣喔~

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.noahliu.draggablebuttondemo.DraggableButton
        android:id="@+id/button_Hello"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        />
    
</androidx.constraintlayout.widget.ConstraintLayout>

 

然後使用之

 

MainActivity.java

public class MainActivity extends AppCompatActivity {
    public static final String TAG = MainActivity.class.getSimpleName()+"My";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DraggableButton btHello = findViewById(R.id.button_Hello);
        btHello.setOnClickListener(v->{
            Toast.makeText(this, "Hello", Toast.LENGTH_SHORT).show();
        });
    }
}

 


 

其實寫完後,發現這也不是多了不起的功能麻(゜ロ゜)

是的,其實我之前學寫這個的時候想說很難,寫了一大堆東西

後來才想到..靠,既然是按鈕不就直接繼承按鈕就好??

所以其實當時也是走了不少冤枉路的

那希望有緣看到這篇文的你~在寫程式的路上可以少點冤枉路囉:D

最後..

TK

arrow
arrow
    創作者介紹
    創作者 碼農日常 的頭像
    碼農日常

    碼農日常大小事

    碼農日常 發表在 痞客邦 留言(22) 人氣()