1. 程式人生 > >Android 自定義View 之 可隨意拖動的View

Android 自定義View 之 可隨意拖動的View

因為趕專案本人停更兩個月 從今天開始又可以更新了 今天說一下這個可隨意拖動的view 簡單說一下這個view效果 和 發展 一開始這種效果是使用在網頁端的特別是購物類 例如某寶 某東 購物車和客服視窗 都有使用這個懸浮可拖動的設計效果 後來才發展到的移動端 還有手機桌面也是用到了這種效果 例如某族手機的訊息中心 手機桌面的懸浮球 某訊 和 某榮耀手遊 某吃雞遊戲 某視訊軟體等等也都是這種效果 這種方式的好處就是是可以隨時隨地的快速進去到你需要的頁面 目前來說 有很多的大型app選擇了這種互動方式 服務使用者 ;
下面說一下android 端的使用及實現

先看一下效果圖
在這裡插入圖片描述

編碼思路

首先實現這種效果的方式有兩種 一是自定義viewgroup 自定義容器 需要獲取手機懸浮窗許可權和處理各種事件 二 使用自定義view 第一種方式相對簡單一些 但是使用效果不好 所以我也就放棄掉了
今天主要說一下通過自定義view 的方式實現

自定義的主要思路 : 以ImageView為例

建立FreeView 繼承 ImageView

首先 獲取最大高度和寬度 設定view 的活動範圍 (主要考慮是否支援螢幕外 具體細節下面再說)

第二 通過onTouchEvent 觸控事件進行 位移計算 重置FreeView 的四角座標

第三 就是完成顯示

主要思路就這麼多 我的重點是第一步和第二步

注意
第一步 獲取最大高度和寬度 就是給定FreeView的活動範圍 最大寬度很容易獲取 需要注意的是最大高度 我們螢幕組成

window = statusbar + layout + navigationbar (從上倒下的順序)

受android碎片化影響 andorid手機的高度具有很多種不確定性 所以要做好高度的適配

還有第二個要注意的地方 這裡跟你的需求有關係 就是是否允許FreeView 出現在螢幕外 這種需求例如魅族手機的訊息中心 顆拖到螢幕邊緣 留下一個小尾巴 點選或者拖動時在展現給使用者

第二步 這裡主要是實現計算 和 實現方式 首先 onTouchEvent 通過觸控監聽MotionEvent.ACTION_DOWN 我可以得到點選時的座標 通過MotionEvent.ACTION_MOVE 可以得到離開時的座標 然後通過給FreeView設定layout 實現 位置重置 在這裡還要注意點選事件和拖動時間的衝突處理 程式碼中會有體現

import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ImageView;

import com.nade.commenlib.util.UiUtils;

/**
 *Created by Nade on 2018/10/2.
 *隨意拖動的view
 */

@SuppressLint("AppCompatCustomView")
public class FreeView extends ImageView {

    private int width; //  測量寬度 FreeView的寬度
    private int height; // 測量高度 FreeView的高度
    private int maxWidth; // 最大寬度 window 的寬度
    private int maxHeight; // 最大高度 window 的高度
    private Context context;
    private float downX; //點選時的x座標
    private float downY;  // 點選時的y座標
    //是否拖動標識
    private boolean isDrag=false;

    // 處理點選事件和滑動時間衝突時使用 返回是否拖動標識
    public boolean isDrag() {
        return isDrag;
    }

    // 初始化屬性
    public FreeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context=context;


    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 獲取屏寬高 和 可是適用範圍 (我的需求是可在螢幕內拖動 不超出範圍 也不需要隱藏)
        width=getMeasuredWidth();
        height=getMeasuredHeight();
        maxWidth = UiUtils.getMaxWidth(context);
        //   maxHeight = UiUtils.getMaxHeight(context)-getStatusBarHeight();// 此時減去狀態列高度 注意如果有狀態列 要減去狀態列 如下行 得到的是可活動的高度
       maxHeight = UiUtils.getMaxHeight(context)-getStatusBarHeight() - getNavigationBarHeight();
    }
    // 獲取狀態列高度
    public int getStatusBarHeight(){
        int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
        return getResources().getDimensionPixelSize(resourceId);
    }
    // 獲取導航欄高度
    public int getNavigationBarHeight() {
        int rid = getResources().getIdentifier("config_showNavigationBar", "bool", "android");
        if (rid!=0){
            int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
            return context.getResources().getDimensionPixelSize(resourceId);
        }else
            return 0;

    }


    /**
     * 處理事件分發
     * @param event
     * @return
     */

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        if (this.isEnabled()) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN: // 點選動作處理 每次點選時將拖動狀態改為 false 並且記錄下點選時的座標 downX downY
                    isDrag=false;
                    downX = event.getX(); // 點選觸屏時的x座標 用於離開螢幕時的x座標作計算
                    downY = event.getY(); // 點選觸屏時的y座標 用於離開螢幕時的y座標作計算
                    break;
                case MotionEvent.ACTION_MOVE: // 滑動動作處理 記錄離開螢幕時的 moveX  moveY 用於計算距離 和 判斷滑動事件和點選事件 並作出響應
                    final float moveX = event.getX() - downX;
                    final float moveY = event.getY() - downY;
                    int l,r,t,b; // 上下左右四點移動後的偏移量
                    //計算偏移量 設定偏移量 = 3 時 為判斷點選事件和滑動事件的峰值
                    if (Math.abs(moveX) > 3 ||Math.abs(moveY) > 3) { // 偏移量的絕對值大於 3 為 滑動時間 並根據偏移量計算四點移動後的位置
                        l = (int) (getLeft() + moveX);
                        r = l+width;
                        t = (int) (getTop() + moveY);
                        b = t+height;
                        //不劃出邊界判斷,最大值為邊界值
                        // 如果你的需求是可以劃出邊界 此時你要計算可以劃出邊界的偏移量 最大不能超過自身寬度或者是高度  如果超過自身的寬度和高度 view 劃出邊界後 就無法再拖動到介面內了 注意
                        if(l<0){ // left 小於 0 就是滑出邊界 賦值為 0 ; right 右邊的座標就是自身寬度 如果可以劃出邊界 left right top bottom 最小值的絕對值 不能大於自身的寬高
                            l=0;
                            r=l+width;
                        }else if(r> maxWidth){ // 判斷 right 並賦值
                            r= maxWidth;
                            l=r-width;
                        }
                        if(t<0){ // top
                            t=0;
                            b=t+height;
                        }else if(b> maxHeight){ // bottom
                            b= maxHeight;
                            t=b-height;
                        }
                        this.layout(l, t, r, b); // 重置view在layout 中位置
                        isDrag=true;  // 重置 拖動為 true
                    }else {
                        isDrag=false; // 小於峰值3時 為點選事件
                    }
                    break;
                case MotionEvent.ACTION_UP: // 不處理
                    setPressed(false);
                    break;
                case MotionEvent.ACTION_CANCEL: // 不處理
                    setPressed(false);
                    break;
            }
            return true;
        }
        return false;
    }
}

註釋寫的很清晰了 不懂得可以看程式碼 我說一下點選事件的處理 我暴漏了一個方法
public boolean isDrag() {
return isDrag;
}
這個方法返回一個boolean 適用於標識是否滑動 我們利用這個值進行區分點選事件和滑動事件
好 看一下使用

freeview = findViewById(R.id.main_freeview);
freeview.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (!freeview.isDrag()) {
Toast.makeText(MainActivity.this, “點選了freeview”, Toast.LENGTH_SHORT).show();
}
}
});

此時只要處理點選事件即可
好了 本文到此完結 如有疑問歡迎私信和留言 原始碼在這裡就不上傳了 有需要可以加我的群 675234574 或加我微信

下面是工具類

import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;

public class UiUtil {
// 獲取最大寬度
public static int getMaxWidth(Context context) {
WindowManager wm = (WindowManager) context.getSystemService( Context.WINDOW_SERVICE );
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics( dm );
return dm.widthPixels;
}
// 獲取最大高度
public static int getMaxHeight(Context context) {
WindowManager wm = (WindowManager) context.getSystemService( Context.WINDOW_SERVICE );
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics( dm );
return dm.heightPixels;
}
}