Android可拖动可吸附悬浮窗

Android可拖动可吸附悬浮窗,第1张

第一次写稿,写的不好,请大神多提建议


前言

前一段时间由于项目需要,写了一个可拖动可靠边吸附的悬浮窗,特意记录下来,方便大家一起学习


一、FloatingViewMagnet

悬浮窗的吸附管理类,代码示例:

package com.example.myapplication;

import android.content.Context;
import android.content.res.Configuration;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
 * 吸附悬浮窗管理类
 * @author zgp
 * @data 2022/2/17
 */
public class FloatingViewMagnet extends FrameLayout {

    public static final int MARGIN_EDGE = 13;
    private static final int TOUCH_TIME_THRESHOLD = 150;
    private float mOriginalRawX;
    private float mOriginalRawY;
    private float mOriginalX;
    private float mOriginalY;
    private MagnetViewListener mMagnetViewListener;
    private long mLastTouchDownTime;
    protected MoveAnimator mMoveAnimator;
    protected int mScreenWidth;
    private int mScreenHeight;
    private int mStatusBarHeight;
    private boolean isNearestLeft = true;
    private float mPortraitY;
    private Context mContext;

    public FloatingViewMagnet(@NonNull Context context) {
        this(context, null);
    }

    public FloatingViewMagnet(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public FloatingViewMagnet(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
        init();
    }

    private void init() {
        mMoveAnimator = new MoveAnimator();
        mStatusBarHeight = AppUtils.INSTANCE.getStatusBarHeight(mContext);
        setClickable(true);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event == null) {
            return false;
        }
        switch (event.getAction()) {
            // 手指按下状态
            case MotionEvent.ACTION_DOWN:
                changeOriginalTouchParams(event);
                updateSize();
                mMoveAnimator.stop();
                break;
                // 手指拖动状态
            case MotionEvent.ACTION_MOVE:
                updateViewPosition(event);
                break;
                // 手指抬起状态
            case MotionEvent.ACTION_UP:
                clearPortraitY();
                moveToEdge();
                if (isOnClickEvent()) {
                    dealClickEvent();
                }
                break;
        }
        return true;
    }

    protected void dealClickEvent() {
        if (mMagnetViewListener != null) {
            mMagnetViewListener.onClick(this);
        }
    }

    protected boolean isOnClickEvent() {
        return System.currentTimeMillis() - mLastTouchDownTime < TOUCH_TIME_THRESHOLD;
    }

    public void moveToEdge() {
        moveToEdge(isNearestLeft(), false);
    }

    private void moveToEdge(boolean isLeft, boolean isLandscape) {
        float moveDistance = isLeft ? MARGIN_EDGE : mScreenWidth - MARGIN_EDGE;
        float y = getY();
        if (!isLandscape && mPortraitY != 0) {
            y = mPortraitY;
            clearPortraitY();
        }
        mMoveAnimator.start(moveDistance, Math.min(Math.max(0, y), mScreenHeight - getHeight()));
    }

    protected boolean isNearestLeft() {
        int middle = mScreenWidth / 2;
        isNearestLeft = getX() < middle;
        return isNearestLeft;
    }

    private void clearPortraitY() {
        mPortraitY = 0;
    }

    private void updateViewPosition(MotionEvent event) {
        setX(mOriginalX + event.getRawX() - mOriginalRawX);
        // 限制不可超出屏幕高度
        float desY = mOriginalY + event.getRawY() - mOriginalRawY;
        if (desY < mStatusBarHeight) {
            desY = mStatusBarHeight;
        }
        if (desY > mScreenHeight - getHeight()) {
            desY = mScreenHeight - getHeight();
        }
        setY(desY);
    }

    private void updateSize() {
        ViewGroup viewGroup = (ViewGroup) getParent();
        if (viewGroup != null) {
            mScreenWidth = viewGroup.getWidth() - getWidth();
            mScreenHeight = viewGroup.getHeight();
        }
    }

    private void changeOriginalTouchParams(MotionEvent event) {
        mOriginalX = getX();
        mOriginalY = getY();
        mOriginalRawX = event.getRawX();
        mOriginalRawY = event.getRawY();
        mLastTouchDownTime = System.currentTimeMillis();
    }

    public void onRemove() {
        if (mMagnetViewListener != null) {
            mMagnetViewListener.onRemove(this);
        }
    }

    public void setMagnetViewListener(MagnetViewListener magnetViewListener) {
        this.mMagnetViewListener = magnetViewListener;
    }

    private class MoveAnimator implements Runnable {

        private Handler handler = new Handler(Looper.getMainLooper());
        private float destinationX;
        private float destinationY;
        private long startingTime;

        void start(float x, float y) {
            this.destinationX = x;
            this.destinationY = y;
            startingTime = System.currentTimeMillis();
            handler.post(this);
        }

        @Override
        public void run() {
            if (getRootView() == null || getRootView().getParent() == null) {
                return;
            }
            float progress = Math.min(1, (System.currentTimeMillis() - startingTime) / 400f);
            float deltaX = (destinationX - getX()) * progress;
            float deltaY = (destinationY - getY()) * progress;
            move(deltaX, deltaY);
            if (progress < 1) {
                handler.post(this);
            }
        }

        private void stop() {
            handler.removeCallbacks(this);
        }
    }

    private void move(float deltaX, float deltaY) {
        setX(getX() + deltaX);
        setY(getY() + deltaY);
    }

    @Override
    protected void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (getParent() != null) {
            final boolean isLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE;
            markPortraitY(isLandscape);
            ((ViewGroup)getParent()).post(new Runnable() {
                @Override
                public void run() {
                    updateSize();
                    moveToEdge(isNearestLeft, isLandscape);
                }
            });
        }
    }

    private void markPortraitY(boolean isLandscape) {
        if (isLandscape) {
            mPortraitY = getY();
        }
    }
}
2.FloatingView

悬浮窗,代码如下(示例):

package com.example.myapplication

import android.app.Activity
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.RelativeLayout
import android.R
import androidx.activity.ComponentActivity
import androidx.core.view.ViewCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import java.lang.ref.WeakReference

/**
 * <悬浮窗管理器>
 * @author zgp
 * @data 2022/2/17
 */
object FloatingView: BaseFloatingBiew, LifecycleObserver {

    private var sInstance: FloatingView? = null
    // 悬浮窗吸附管理类对象
    private var mFloatingViewMagnet: FloatingViewMagnet? = null
    private var mContainer: WeakReference? = null
    private var mLayoutParams: ViewGroup.LayoutParams = getParams()
    private var mContext: Context? = null

    open fun getInstance(context: Context): FloatingView {
        if (sInstance == null) {
            synchronized(FloatingView::class.java) {
                if (sInstance == null) {
                    sInstance = FloatingView
                    mContext = context
                    if (context is ComponentActivity) {
                        (context as ComponentActivity).lifecycle.addObserver(this)
                    }
                }
            }
        }
        return sInstance!!
    }
    override fun remove() {
        Handler(Looper.getMainLooper()).post(object : Runnable{
            override fun run() {
                if (mFloatingViewMagnet == null) {
                    return
                }
                if (ViewCompat.isAttachedToWindow(mFloatingViewMagnet!!) && getContainer() != null) {
                    getContainer()?.removeView(mFloatingViewMagnet)
                }
                mFloatingViewMagnet = null
            }

        })
        if (sInstance != null) {
            sInstance = null
        }
    }

    private fun ensureFloatingView(context: Context) {
        synchronized(this) {
            if (mFloatingViewMagnet != null) {
                return
            }
            mFloatingViewMagnet = EnFloatingView(context)
            mFloatingViewMagnet?.layoutParams = mLayoutParams
            // 关闭按钮的点击监听
            (mFloatingViewMagnet as EnFloatingView).mCloseBUtton?.setOnClickListener {
                mFloatingViewMagnet?.onRemove()
                remove()
            }
            addViewToWindow(mFloatingViewMagnet!!)
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun onStart() {
//        AppLog.d("FloatingView", "onResume, mContext=$mContext")
        attach(mContext as Activity)
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun onStop() {
//        AppLog.d("FloatingView", "onStop, mContext=$mContext")
        detach(mContext as Activity)
    }

    /**
     * 添加显示悬浮窗
     */
    override fun add(): FloatingView {
        AppLog.d("FloatingView", "add, mContext=$mContext")
        ensureFloatingView(mContext!!)
        return this
    }

    /**
     * 注册悬浮窗
     */
    override fun attach(activity: Activity): FloatingView {
        getActivityRoot(activity)?.let { attach(it) }
        return this
    }

    private fun attach(frameLayout: FrameLayout) {
        if (frameLayout == null || mFloatingViewMagnet == null) {
            mContainer = WeakReference(frameLayout)
            return
        }
        if (mFloatingViewMagnet!!.parent == frameLayout) {
            return
        }
        if (mFloatingViewMagnet!!.parent != null) {
            (mFloatingViewMagnet!!.parent as ViewGroup).removeView(mFloatingViewMagnet)
        }
        mContainer = WeakReference(frameLayout)
        frameLayout.addView(mFloatingViewMagnet)
    }

    /**
     *
     */
    override fun detach(activity: Activity): FloatingView {
        getActivityRoot(activity)?.let { detach(it) }
        return this
    }

     private fun detach(frameLayout: FrameLayout) {
        if (mFloatingViewMagnet != null && frameLayout != null && ViewCompat.isAttachedToWindow(mFloatingViewMagnet!!)) {
            frameLayout.removeView(mFloatingViewMagnet)
            mFloatingViewMagnet = null
        }
        if (getContainer() == frameLayout) {
            mContainer = null
        }
         if (sInstance != null) {
             sInstance = null
         }
    }

    override fun getView(): FloatingViewMagnet? {
        return mFloatingViewMagnet
    }

    override fun customView(viewGroup: FloatingViewMagnet): FloatingView {
        mFloatingViewMagnet = viewGroup
        return this
    }

    override fun layoutParams(params: ViewGroup.LayoutParams): FloatingView {
        mLayoutParams = params
        mFloatingViewMagnet?.layoutParams = params
        return this
    }

    override fun listener(magnetViewListener: MagnetViewListener): FloatingView {
        mFloatingViewMagnet?.setMagnetViewListener(magnetViewListener)
        return this
    }

    private fun addViewToWindow(view: View) {
        if (getContainer() == null) {
            return
        }
        getContainer()?.addView(view)
    }

    private fun getContainer(): FrameLayout?{
        if (mContainer == null) {
            return null
        }
        return mContainer!!.get()
    }

    private fun getParams(): FrameLayout.LayoutParams {
        var params = FrameLayout.LayoutParams(
                RelativeLayout.LayoutParams.WRAP_CONTENT,
                RelativeLayout.LayoutParams.WRAP_CONTENT
        )
        params.gravity = Gravity.CENTER_VERTICAL
        params.setMargins(15, params.topMargin, params.rightMargin, params.bottomMargin)
        return params
    }

    private fun getActivityRoot(activity: Activity): FrameLayout? {
        try {
            return activity.window.decorView.findViewById(R.id.content)
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return null
    }
}

该类我加入了Lifecycle,是为了感知Activity生命周期的变化,不用再手动的去关闭悬浮窗。

 3.MainActivity使用
public class MainActivity extends AppCompatActivity {

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

    private void initView() {
        FloatingView.INSTANCE.add().listener(new MagnetViewListener() {
            @Override
            public void onRemove(@NotNull FloatingViewMagnet magnetView) {
                
            }

            @Override
            public void onClick(@NotNull FloatingViewMagnet magnetView) {
                // 点击监听方法
            }
        });
    }
}

最后感谢简书的这篇文章对我的启发安卓可拖拽悬浮按钮二,文章里面有吸附的效果,如果项目中需要可以参考下~

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/992567.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-21
下一篇2022-05-21

发表评论

登录后才能评论

评论列表(0条)

    保存