1. 程式人生 > >Android自定義可拖拽的懸浮按鈕---DragFloatingActionButton

Android自定義可拖拽的懸浮按鈕---DragFloatingActionButton

懸浮按鈕FloatingActionButton是Android 5.0系統新增的新控制元件,FloatingActionButton是繼承至ImageView,所以FloatingActionButton擁有ImageView的所有屬性。本文講解的是一個實現了可拖拽的懸浮按鈕,併為此添加了類似於qq的吸附邊框的功能。在此之前,先了解下其簡單的使用方式吧:
首先你得新增其依賴

compile 'com.android.support:design:25.3.1'

然後在佈局檔案中使用。

<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right|bottom" android:src="@drawable/ic_launcher" />

如圖:
這裡寫圖片描述
FloatingActionButton正常顯示的情況下有個填充的顏色,有個陰影;點選的時候會有一個rippleColor,並且陰影的範圍可以增大。其中:
1、填充的顏色預設使用就是style當中的colorAccent。
2、rippleColor預設取的是Theme當中的colorControlHighlight。
3、elevation和pressedTranslationZ,前者使用者設定正常顯示的陰影大小;後者是點選時顯示的陰影大小。
好了,現在介紹本文的重點:可拖拽的,有吸附功能的懸浮按鈕
先上程式碼。

import android.animation.ObjectAnimator;
import android.content.Context;
import android.support.design.widget.FloatingActionButton;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.animation.DecelerateInterpolator;

public class DragFloatActionButton
extends FloatingActionButton {
private int screenWidth; private int screenHeight; private int screenWidthHalf; private int statusHeight; private int virtualHeight; public DragFloatActionButton(Context context) { super(context); init(); } public DragFloatActionButton(Context context, AttributeSet attrs) { super(context, attrs); init(); } public DragFloatActionButton(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { screenWidth = ScreenUtils.getScreenWidth(getContext()); screenWidthHalf = screenWidth / 2; screenHeight = ScreenUtils.getScreenHeight(getContext()); statusHeight = ScreenUtils.getStatusHeight(getContext()); virtualHeight=ScreenUtils.getVirtualBarHeigh(getContext()); } private int lastX; private int lastY; private boolean isDrag; @Override public boolean onTouchEvent(MotionEvent event) { int rawX = (int) event.getRawX(); int rawY = (int) event.getRawY(); switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: isDrag = false; getParent().requestDisallowInterceptTouchEvent(true); lastX = rawX; lastY = rawY; Log.e("down---->", "getX=" + getX() + ";screenWidthHalf=" + screenWidthHalf); break; case MotionEvent.ACTION_MOVE: isDrag = true; //計算手指移動了多少 int dx = rawX - lastX; int dy = rawY - lastY; //這裡修復一些手機無法觸發點選事件的問題 int distance= (int) Math.sqrt(dx*dx+dy*dy); Log.e("distance---->",distance+""); if(distance<3){//給個容錯範圍,不然有部分手機還是無法點選 isDrag=false; break; } float x = getX() + dx; float y = getY() + dy; //檢測是否到達邊緣 左上右下 x = x < 0 ? 0 : x > screenWidth - getWidth() ? screenWidth - getWidth() : x; // y = y < statusHeight ? statusHeight : (y + getHeight() >= screenHeight ? screenHeight - getHeight() : y); if (y<0){ y=0; } if (y>screenHeight-statusHeight-getHeight()){ y=screenHeight-statusHeight-getHeight(); } setX(x); setY(y); lastX = rawX; lastY = rawY; Log.e("move---->", "getX=" + getX() + ";screenWidthHalf=" + screenWidthHalf + " " + isDrag+" statusHeight="+statusHeight+ " virtualHeight"+virtualHeight+ " screenHeight"+ screenHeight+" getHeight="+getHeight()+" y"+y); break; case MotionEvent.ACTION_UP: if (isDrag) { //恢復按壓效果 setPressed(false); Log.e("ACTION_UP---->", "getX=" + getX() + ";screenWidthHalf=" + screenWidthHalf); if (rawX >= screenWidthHalf) { animate().setInterpolator(new DecelerateInterpolator()) .setDuration(500) .xBy(screenWidth - getWidth() - getX()) .start(); } else { ObjectAnimator oa = ObjectAnimator.ofFloat(this, "x", getX(), 0); oa.setInterpolator(new DecelerateInterpolator()); oa.setDuration(500); oa.start(); } } Log.e("up---->",isDrag+""); break; } //如果是拖拽則消耗事件,否則正常傳遞即可。 return isDrag || super.onTouchEvent(event); } }

ScreenUtils.java

package com.example.cmos.retrofitdemo;

import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.Window;
import android.view.WindowManager;

import java.lang.reflect.Method;

/**
 * Created by gongwq on 2017/6/14 0014.
 */

public class ScreenUtils {

    private ScreenUtils() {
        /* cannot be instantiated */
        throw new UnsupportedOperationException("cannot be instantiated");
    }

    /**
     * 獲得螢幕高度
     *
     * @param context
     * @return
     */
    public static int getScreenWidth(Context context) {
        WindowManager wm = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        return outMetrics.widthPixels;
    }

    /**
     * 獲得螢幕寬度
     *
     * @param context
     * @return
     */
    public static int getScreenHeight(Context context) {
        WindowManager wm = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        return outMetrics.heightPixels;
    }

    /**
     * 獲得狀態列的高度
     *
     * @param context
     * @return
     */
    public static int getStatusHeight(Context context) {

        int statusHeight = -1;
        try {
            Class<?> clazz = Class.forName("com.android.internal.R$dimen");
            Object object = clazz.newInstance();
            int height = Integer.parseInt(clazz.getField("status_bar_height")
                    .get(object).toString());
            statusHeight = context.getResources().getDimensionPixelSize(height);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return statusHeight;
    }

    /**
     * 獲取虛擬功能鍵高度
     */
    public static int getVirtualBarHeigh(Context context) {
        int vh = 0;
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = windowManager.getDefaultDisplay();
        DisplayMetrics dm = new DisplayMetrics();
        try {
            @SuppressWarnings("rawtypes")
            Class c = Class.forName("android.view.Display");
            @SuppressWarnings("unchecked")
            Method method = c.getMethod("getRealMetrics", DisplayMetrics.class);
            method.invoke(display, dm);
            vh = dm.heightPixels - windowManager.getDefaultDisplay().getHeight();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return vh;
    }

    public static int getVirtualBarHeigh(Activity activity) {
        int titleHeight = 0;
        Rect frame = new Rect();
        activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
        int statusHeight = frame.top;
        titleHeight = activity.getWindow().findViewById(Window.ID_ANDROID_CONTENT).getTop() - statusHeight;
        return titleHeight;
    }

}

上面的程式碼也很簡單,相信看程式碼中的註釋就可以看的明白了。但是這裡還是講下其實現原理:這個自定義的懸浮按鈕,我們主要是重寫了其onTouch事件,捕捉觸控事件,然後利用setX(),setY()方法將其移動。而吸附效果,主要是利用的屬性動畫,最後,不要忘了return 是否還在拖拽的結果,免得無法觸發點選事件。

PS
最後貼一個彈出框。推薦用popmenu,相比於popwindow,這個會自動調整顯示的位置,這在拖拽的懸浮按鈕中很有用,因為如果用後者,你將按鈕移到螢幕上方,而當你的彈出框也是設定在顯示的懸浮按鈕的上方,那麼就有可能會遮擋彈出框的內容。


        dragFloatActionButton= (DragFloatActionButton) findViewById(R.id.floatBtn);
        dragFloatActionButton.setOnClickListener(this);
....

   @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.floatBtn:

                PopupMenu popupMenu=new PopupMenu(this,view);
                getMenuInflater().inflate(R.menu.pop_item,popupMenu.getMenu());
                popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
                    @Override
                    public boolean onMenuItemClick(MenuItem menuItem) {
                        switch (menuItem.getItemId()){
                            case R.id.action_last:
                                Toast.makeText(TestActivity.this,""+menuItem.getItemId(),Toast.LENGTH_SHORT).show();
                                break;
                            case R.id.action_next:
                                Toast.makeText(TestActivity.this,""+menuItem.getItemId(),Toast.LENGTH_SHORT).show();
                                break;
                        }

                        return false;
                    }
                });
                popupMenu.show();
                Log.e("****--->","float");
               // Toast.makeText(this,"flaot---",Toast.LENGTH_SHORT).show();
                break;
        }
    }

新建menu資料夾,在裡面新增pop_item.xml檔案

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

    <item
        android:id="@+id/action_delete"
        android:orderInCategory="100"
        android:title="刪除"
        app:showAsAction="never" />
    <item
        android:id="@+id/action_save"
        android:orderInCategory="200"
        android:title="儲存"
        app:showAsAction="never" />
    <item
        android:id="@+id/action_last"
        android:orderInCategory="300"
        android:title="上一步"
        app:showAsAction="never" />
    <item
        android:id="@+id/action_next"
        android:icon="@null"
        android:orderInCategory="400"
        android:title="下一步"
        app:showAsAction="never" />

</menu>