自定义控件

Android(java)应用内部无需权限悬浮可拖动的View

2022-07-06  本文已影响0人  想看烟花么

#######IFloatingView

package com.xxx.xxxxx.view.floatingview;

import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.LayoutRes;

import org.jetbrains.annotations.NotNull;

public interface IFloatingView {
    ChatFloatingView remove();

    ChatFloatingView show(ViewGroup container, @LayoutRes int layoutId, @NotNull ViewGroup.LayoutParams layoutParams);

    ChatFloatingView show(ViewGroup container, @LayoutRes int layoutId, int left, int top, int right, int bottom, FloatingGravity gravity);

    View getContentView();

    ChatFloatingView setListener(View.OnClickListener viewListener);

}

#######FloatingGravity

package com.xxx.xxxx.view.floatingview;
public enum FloatingGravity {
    LEFT_TOP,
    LEFT_BOTTOM,
    RIGHT_TOP,
    RIGHT_BOTTOM
}

#######ChatFloatingView

package com.xxx.xxxx.view.floatingview;

import android.os.Handler;
import android.os.Looper;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;

import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.view.ViewCompat;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.ref.WeakReference;

public class ChatFloatingView implements IFloatingView {
    private static volatile ChatFloatingView mInstance;
    private WeakReference<ViewGroup> mContainer;
    private WeakReference<View> mFloatingView = null;

    private ChatFloatingView() {
    }

    public static ChatFloatingView get() {
        if (mInstance == null) {
            synchronized (ChatFloatingView.class) {
                if (mInstance == null) {
                    mInstance = new ChatFloatingView();
                }
            }
        }
        return mInstance;
    }

    @Override
    public ChatFloatingView remove() {
        new Handler(Looper.getMainLooper()).post(() -> {
            if (getFloatingView() == null) {
                return;
            }
            if (ViewCompat.isAttachedToWindow(getFloatingView()) && getContainer() != null) {
                getContainer().removeView(getFloatingView());
            }
        });
        return this;
    }

    @Override
    public ChatFloatingView show(ViewGroup container, @LayoutRes int layoutId, @NonNull ViewGroup.LayoutParams layoutParams) {
        add2ViewGroup(container, layoutId, layoutParams);
        return this;
    }

    /**
     * @param container | only support [FrameLayout,ConstraintLayout,RelativeLayout]
     * @return this
     */
    @Override
    public ChatFloatingView show(ViewGroup container, @LayoutRes int layoutId, int left, int top, int right, int bottom, FloatingGravity gravity) {
        ViewGroup.MarginLayoutParams marginLayoutParams = getMarginLayoutParams(container, gravity);
        marginLayoutParams.setMargins(left, top, right, bottom);
        add2ViewGroup(container, layoutId, marginLayoutParams);
        return this;
    }

    private ViewGroup.MarginLayoutParams getMarginLayoutParams(ViewGroup container, FloatingGravity gravity) {
        if (container instanceof FrameLayout) {
            FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
                    ViewGroup.LayoutParams.WRAP_CONTENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
            layoutParams.gravity = getFrameLayoutGravity(gravity);
            return layoutParams;
        } else if (container instanceof ConstraintLayout) {
            ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
            if (gravity == FloatingGravity.LEFT_TOP) {
                layoutParams.startToStart = ConstraintLayout.LayoutParams.PARENT_ID;
                layoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID;
            } else if (gravity == FloatingGravity.LEFT_BOTTOM) {
                layoutParams.startToStart = ConstraintLayout.LayoutParams.PARENT_ID;
                layoutParams.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID;
            } else if (gravity == FloatingGravity.RIGHT_TOP) {
                layoutParams.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID;
                layoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID;
            } else {
                layoutParams.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID;
                layoutParams.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID;
            }
            return layoutParams;
        } else if (container instanceof RelativeLayout) {
            RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
            if (gravity == FloatingGravity.LEFT_TOP) {
                layoutParams.addRule(RelativeLayout.ALIGN_PARENT_START);
                layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
            } else if (gravity == FloatingGravity.LEFT_BOTTOM) {
                layoutParams.addRule(RelativeLayout.ALIGN_PARENT_START);
                layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
            } else if (gravity == FloatingGravity.RIGHT_TOP) {
                layoutParams.addRule(RelativeLayout.ALIGN_PARENT_END);
                layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
            } else {
                layoutParams.addRule(RelativeLayout.ALIGN_PARENT_END);
                layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
            }
            return layoutParams;
        } else {
            FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
                    ViewGroup.LayoutParams.WRAP_CONTENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
            layoutParams.gravity = getFrameLayoutGravity(gravity);
            return layoutParams;
        }
    }

    private int getFrameLayoutGravity(FloatingGravity gravity) {
        int newGravity;
        if (gravity == FloatingGravity.LEFT_TOP) {
            newGravity = Gravity.START | Gravity.TOP;
        } else if (gravity == FloatingGravity.LEFT_BOTTOM) {
            newGravity = Gravity.START | Gravity.BOTTOM;
        } else if (gravity == FloatingGravity.RIGHT_TOP) {
            newGravity = Gravity.END | Gravity.TOP;
        } else {
            newGravity = Gravity.END | Gravity.BOTTOM;
        }
        return newGravity;
    }

    private void add2ViewGroup(ViewGroup container, @LayoutRes int layoutId, @NotNull ViewGroup.LayoutParams layoutParams) {
        if (container == null) {
            return;
        }
        if (getFloatingView() == null) {
            LayoutInflater factory = LayoutInflater.from(container.getContext());
            mFloatingView = new WeakReference<>(factory.inflate(layoutId, null));
            mContainer = new WeakReference<>(container);
            getFloatingView().setLayoutParams(layoutParams);
            container.addView(getFloatingView());
            return;
        }
        ViewGroup parentView = (ViewGroup) getFloatingView().getParent();
        if (parentView != container) {
            mContainer = new WeakReference<>(container);
        } else {
            container.removeView(getFloatingView());
        }
        getFloatingView().setLayoutParams(layoutParams);
        container.addView(getFloatingView());
    }

    @Override
    public View getContentView() {
        return getFloatingView();
    }

    @Override
    public ChatFloatingView setListener(View.OnClickListener viewListener) {
        return this;
    }

    @Nullable
    private ViewGroup getContainer() {
        if (mContainer == null) {
            return null;
        }
        return mContainer.get();
    }

    @Nullable
    private View getFloatingView() {
        if (mFloatingView == null) {
            return null;
        }
        return mFloatingView.get();
    }

}

#######FloatingViewListener

package com.xxx.xxxxx.view.floatingview;

public interface FloatingViewListener {
    void onRemove(FloatingMagnetLayout floatingLayout);

    void onClick(FloatingMagnetLayout floatingLayout);
}

#######FloatingMagnetLayout

package com.xxx.xxxx.view.floatingview;

import android.annotation.SuppressLint;
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 androidx.constraintlayout.widget.ConstraintLayout;

import com.greendotcorp.chatsdk.utils.AppUtils;

public class FloatingMagnetLayout extends ConstraintLayout {

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

    public void setFloatingViewListener(FloatingViewListener magnetViewListener) {
        this.mFloatingViewListener = magnetViewListener;
    }

    public FloatingMagnetLayout(Context context) {
        this(context, null);
    }

    public FloatingMagnetLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public FloatingMagnetLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mMoveAnimator = new MoveAnimator();
//        mStatusBarHeight = AppUtils.getStatusBarHeight(getContext());
        mStatusBarHeight = 0;
        setClickable(true);
//        updateSize();
    }

    @SuppressLint("ClickableViewAccessibility")
    @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 (mFloatingViewListener != null) {
            mFloatingViewListener.onClick(this);
        }
    }

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

    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 changeOriginalTouchParams(MotionEvent event) {
        mOriginalX = getX();
        mOriginalY = getY();
        mOriginalRawX = event.getRawX();
        mOriginalRawY = event.getRawY();
        mLastTouchDownTime = System.currentTimeMillis();
    }

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

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

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

    private void clearPortraitY() {
        mPortraitY = 0;
    }

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

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

    protected class MoveAnimator implements Runnable {

        private final 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(() -> {
                updateSize();
                moveToEdge(isNearestLeft, isLandscape);
            });
        }
    }

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

-----------------------------End-----------------------------

我也是有底线的,感谢您的耐心阅读,欢迎支持与点赞。
上一篇 下一篇

猜你喜欢

热点阅读