应用内悬浮按钮 可吸附 展开有动画 mini播放器
SystemUtils.java:
public class SystemUtils { public static int getStatusBarHeight(Context context) { int result = 0; int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { result = context.getResources().getDimensionPixelSize(resourceId); } return result; } public static int getScreenWidth(Context context) { int screenWith = -1; try { screenWith = context.getResources().getDisplayMetrics().widthPixels; } catch (Exception e) { e.printStackTrace(); } return screenWith; } public static int getScreenHeight(Context context) { int screenHeight = -1; try { screenHeight = context.getResources().getDisplayMetrics().heightPixels; } catch (Exception e) { e.printStackTrace(); } return screenHeight; } }
布局文件:layout_music_floating_view.xml
是一个头像,加三个按钮:
效果如下:
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/shape_music_floating_view" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="5dp" android:layout_gravity="center" android:orientation="horizontal"> <com.example.m_evolution.View.CircleImageView android:id="@+id/iv_user_avatar" android:layout_width="50dp" android:layout_height="50dp" android:src="@drawable/user_demo" android:layout_gravity="center"/> <LinearLayout android:id="@+id/layout_more" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:orientation="horizontal"> <RelativeLayout android:id="@+id/button_play" android:layout_width="40dp" android:layout_height="40dp" android:layout_gravity="center"> <ImageView android:layout_width="20dp" android:layout_height="20dp" android:layout_centerInParent="true" android:src="@drawable/icon_floating_view_pause"/> </RelativeLayout> <RelativeLayout android:id="@+id/button_cut" android:layout_width="40dp" android:layout_height="40dp" android:layout_gravity="center"> <ImageView android:layout_width="20dp" android:layout_height="20dp" android:layout_centerInParent="true" android:src="@drawable/icon_floating_view_cut"/> </RelativeLayout> <RelativeLayout android:id="@+id/button_like" android:layout_width="40dp" android:layout_height="40dp" android:layout_gravity="center"> <ImageView android:layout_width="20dp" android:layout_height="20dp" android:layout_centerInParent="true" android:src="@drawable/icon_floating_view_like"/> </RelativeLayout> </LinearLayout> </LinearLayout> </LinearLayout> </android.support.constraint.ConstraintLayout>
接着写一个FrameLayout来实现这个按钮,包括动画、吸附等:
import android.animation.ValueAnimator; import android.content.Context; import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.RelativeLayout; import com.example.m_evolution.R; import com.example.m_evolution.Utils.SystemUtils; public class FloatingMagnetView extends FrameLayout { public static final int MARGIN_EDGE = 13; private float mOriginalRawX; private float mOriginalRawY; private float mOriginalX; private float mOriginalY; private OnListener onListener; private static final int TOUCH_TIME_THRESHOLD = 150; private long mLastTouchDownTime; protected MoveAnimator mMoveAnimator; protected int mScreenWidth; private int mScreenHeight; private int mStatusBarHeight; //View private CircleImageView iv_user_avatar; private RelativeLayout button_play; private RelativeLayout button_cut; private RelativeLayout button_like; private LinearLayout layout_more; private int width_layout_more; private boolean is_init_layout_more = false; private String TAG = "FloatingMagnetView"; public void setOnListener(OnListener onListener) { this.onListener = onListener; } public FloatingMagnetView(Context context) { this(context, null); } public FloatingMagnetView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public FloatingMagnetView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); inflate(context, R.layout.layout_music_floating_view, this); iv_user_avatar = findViewById(R.id.iv_user_avatar); button_play = findViewById(R.id.button_play); button_cut = findViewById(R.id.button_cut); button_like = findViewById(R.id.button_like); layout_more = findViewById(R.id.layout_more); init(); } private void init() { layout_more.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { // TODO Auto-generated method stub if(!is_init_layout_more){ is_init_layout_more = true; width_layout_more = layout_more.getWidth(); ViewGroup.LayoutParams params = layout_more.getLayoutParams(); params.width = 0; layout_more.setLayoutParams(params); } } }); mMoveAnimator = new MoveAnimator(); mStatusBarHeight = SystemUtils.getStatusBarHeight(getContext()); setClickable(true); updateSize(); } @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: if (isOnClickEvent(event)) { int[] position = new int[2]; iv_user_avatar.getLocationOnScreen(position); if(mOriginalRawX>=position[0] && mOriginalRawX<=position[0]+iv_user_avatar.getWidth() && mOriginalRawY>=position[1] && mOriginalRawY<=position[1]+iv_user_avatar.getHeight()){ final ViewGroup.LayoutParams params = layout_more.getLayoutParams(); if(params.width == 0){ ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1); valueAnimator.setDuration(500); valueAnimator.start();//开始动画 //注册监听,监听属性值的变化,并设置给目标对象 valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float value = (float) animation.getAnimatedValue(); params.width = (int)(value*width_layout_more); layout_more.setLayoutParams(params); // if(value == 1.0f){ moveToEdge(); // } } }); } else{ ValueAnimator valueAnimator = ValueAnimator.ofFloat(1, 0); valueAnimator.setDuration(500); valueAnimator.start();//开始动画 //注册监听,监听属性值的变化,并设置给目标对象 valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float value = (float) animation.getAnimatedValue(); params.width = (int)(value*width_layout_more); layout_more.setLayoutParams(params); // if(value == 0.0f){ moveToEdge(); // } } }); } return true; } button_play.getLocationOnScreen(position); if(mOriginalRawX>=position[0] && mOriginalRawX<=position[0]+button_play.getWidth() && mOriginalRawY>=position[1] && mOriginalRawY<=position[1]+button_play.getHeight()){ onListener.ClickPlay(); return true; } button_cut.getLocationOnScreen(position); if(mOriginalRawX>=position[0] && mOriginalRawX<=position[0]+button_cut.getWidth() && mOriginalRawY>=position[1] && mOriginalRawY<=position[1]+button_cut.getHeight()){ onListener.ClickCut(); return true; } button_like.getLocationOnScreen(position); if(mOriginalRawX>=position[0] && mOriginalRawX<=position[0]+button_like.getWidth() && mOriginalRawY>=position[1] && mOriginalRawY<=position[1]+button_like.getHeight()){ onListener.ClickLike(); return true; } } else{ moveToEdge(); } break; } return true; } protected boolean isOnClickEvent(MotionEvent event) { if(event.getRawX() == mOriginalRawX &&event.getRawY() == mOriginalRawY){ return (System.currentTimeMillis() - mLastTouchDownTime < TOUCH_TIME_THRESHOLD); } return false; } 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() { mScreenWidth = (SystemUtils.getScreenWidth(getContext()) - this.getWidth()); mScreenHeight = SystemUtils.getScreenHeight(getContext()); } public void moveToEdge() { updateSize(); float moveDistance = isNearestLeft() ? MARGIN_EDGE : mScreenWidth - MARGIN_EDGE; mMoveAnimator.start(moveDistance, getY()); } protected boolean isNearestLeft() { int middle = mScreenWidth / 2; return getX() < middle; } protected 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); } public interface OnListener { void ClickPlay(); void ClickPause(); void ClickCut(); void ClickLike(); } }
接着编写一个类来管理这个按钮:
import android.app.Activity; import android.content.Context; import android.os.Handler; import android.os.Looper; import android.support.v4.view.ViewCompat; import android.view.Gravity; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.RelativeLayout; public class MusicFloatingView{ private FloatingMagnetView mEnFloatingView; // private static volatile MusicFloatingView mInstance; private FrameLayout mContainer; private Context context; public MusicFloatingView(Context context) { this.context = context; } // public static MusicFloatingView get() { // return mInstance; // } public MusicFloatingView remove() { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { if (mEnFloatingView == null) { return; } if (ViewCompat.isAttachedToWindow(mEnFloatingView) && mContainer != null) { mContainer.removeView(mEnFloatingView); } mEnFloatingView = null; } }); return this; } private void ensureMiniPlayer(Context context) { synchronized (this) { if (mEnFloatingView != null) { return; } mEnFloatingView = new FloatingMagnetView(context.getApplicationContext()); mEnFloatingView.setLayoutParams(getParams()); addViewToWindow(mEnFloatingView); } } public MusicFloatingView add() { ensureMiniPlayer(context); return this; } public MusicFloatingView attach(Activity activity) { attach(getActivityRoot(activity)); return this; } public MusicFloatingView attach(FrameLayout container) { if (container == null || mEnFloatingView == null) { mContainer = container; return this; } if (mEnFloatingView.getParent() == container) { return this; } if (mContainer != null && mEnFloatingView.getParent() == mContainer) { mContainer.removeView(mEnFloatingView); } mContainer = container; 、、container.addView(mEnFloatingView); return this; } public MusicFloatingView detach(Activity activity) { detach(getActivityRoot(activity)); return this; } public MusicFloatingView detach(FrameLayout container) { if (mEnFloatingView != null && container != null && ViewCompat.isAttachedToWindow(mEnFloatingView)) { container.removeView(mEnFloatingView); } if (mContainer == container) { mContainer = null; } return this; } public FloatingMagnetView getView() { return mEnFloatingView; } public MusicFloatingView layoutParams(ViewGroup.LayoutParams params) { if (mEnFloatingView != null) { mEnFloatingView.setLayoutParams(params); } return this; } public MusicFloatingView setOnListener(FloatingMagnetView.OnListener onListener) { if (mEnFloatingView != null) { mEnFloatingView.setOnListener(onListener); } return this; } private void addViewToWindow(final FloatingMagnetView view) { if (mContainer == null) { return; } mContainer.addView(view); } private FrameLayout.LayoutParams getParams() { FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); params.gravity = Gravity.BOTTOM | Gravity.START; params.setMargins(13, params.topMargin, params.rightMargin, 56); return params; } private FrameLayout getActivityRoot(Activity activity) { if (activity == null) { return null; } try { return (FrameLayout) activity.getWindow().getDecorView().findViewById(android.R.id.content); } catch (Exception e) { e.printStackTrace(); } return null; } }