一、前言:
我们在开发过程中,需要组合控件完成文字和图片的分别显示:
图片.png
1、自定义AttachButton
package com.sumansoul.teacher.utils.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.CountDownTimer;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.BounceInterpolator;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.sumansoul.teacher.R;
import com.sumansoul.teacher.base.BaseApplication;
/**
* 自定义View实现拖动并自动吸边效果
* <p>
* 处理滑动和贴边 {@link #onTouchEvent(MotionEvent)}
* 处理事件分发 {@link #dispatchTouchEvent(MotionEvent)}
* </p>
*
* @attr customIsAttach //是否需要自动吸边
* @attr customIsDrag //是否可拖曳
*/
public class AttachButton extends RelativeLayout {
private final Context context;
private float mLastRawX;
private float mLastRawY;
private final String TAG = "AttachButton";
private boolean isDrug = false;
private int mRootMeasuredWidth = 0;
private int mRootMeasuredHeight = 0;
private int mRootTopY = 0;
private RelativeLayout rl_attach;
private ImageView iv_alarm;
private TextView tv_alarm;
/**
* left是左侧,right是右侧
*/
private boolean left = false;
private OnCountDownLister onCountDownLister;
boolean mScrolling;
public AttachButton(Context context) {
this(context, null);
}
public AttachButton(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public AttachButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
setClickable(true);
initAttrs(context, attrs);
}
/**
* 初始化自定义属性
*/
private void initAttrs(Context context, AttributeSet attrs) {
View view = LayoutInflater.from(context).inflate(R.layout.attach_button, this);
TypedArray mTypedAttay = context.obtainStyledAttributes(attrs, R.styleable.AttachButton);
mTypedAttay.recycle();
rl_attach = view.findViewById(R.id.rl_attach);
iv_alarm = view.findViewById(R.id.iv_alarm);
tv_alarm = view.findViewById(R.id.tv_alarm);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
super.dispatchTouchEvent(event);
return true;
}
private float touchDownX;
/**
* 点击事件和滑动事件冲突
*
* @param event
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchDownX = event.getX();
mScrolling = false;
break;
case MotionEvent.ACTION_MOVE:
//Log.d("LUO", "真实滑动距离:=====" + Math.abs(touchDownX - event.getX()));
if (Math.abs(touchDownX - event.getX()) >= ViewConfiguration.get(getContext()).getScaledTouchSlop()) {
mScrolling = true;
rl_attach.setBackground(BaseApplication.getInstance().getDrawable(R.drawable.class_center_round_4));
} else {
mScrolling = false;
}
break;
case MotionEvent.ACTION_UP:
mScrolling = false;
break;
}
return mScrolling;
}
/**
* 是否滑动
*/
boolean isScroll = false;
@Override
public boolean onTouchEvent(MotionEvent ev) {
//当前手指的坐标
float mRawX = ev.getRawX();
float mRawY = ev.getRawY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN://手指按下
isScroll = false;
isDrug = false;
//记录按下的位置
mLastRawX = mRawX;
mLastRawY = mRawY;
ViewGroup mViewGroup = (ViewGroup) getParent();
if (mViewGroup != null) {
int[] location = new int[2];
mViewGroup.getLocationInWindow(location);
//获取父布局的高度
mRootMeasuredHeight = mViewGroup.getMeasuredHeight();
mRootMeasuredWidth = mViewGroup.getMeasuredWidth();
//获取父布局顶点的坐标
mRootTopY = location[1];
}
break;
case MotionEvent.ACTION_MOVE://手指滑动
if (mRawX >= 0 && mRawX <= mRootMeasuredWidth && mRawY >= mRootTopY && mRawY <= (mRootMeasuredHeight + mRootTopY)) {
//手指X轴滑动距离
float differenceValueX = mRawX - mLastRawX;
//手指Y轴滑动距离
float differenceValueY = mRawY - mLastRawY;
if (differenceValueX > 0 || differenceValueY > 0) {
isScroll = true;
}
//判断是否为拖动操作
if (!isDrug) {
if (Math.sqrt(differenceValueX * differenceValueX + differenceValueY * differenceValueY) < 2) {
isDrug = false;
} else {
isDrug = true;
rl_attach.setBackground(BaseApplication.getInstance().getDrawable(R.drawable.class_center_round_4));
}
}
//获取手指按下的距离与控件本身X轴的距离
float ownX = getX();
//获取手指按下的距离与控件本身Y轴的距离
float ownY = getY();
//理论中X轴拖动的距离
float endX = ownX + differenceValueX;
//理论中Y轴拖动的距离
float endY = ownY + differenceValueY;
//X轴可以拖动的最大距离
float maxX = mRootMeasuredWidth - getWidth();
//Y轴可以拖动的最大距离
float maxY = mRootMeasuredHeight - getHeight();
//X轴边界限制
endX = endX < 0 ? 0 : endX > maxX ? maxX : endX;
//Y轴边界限制
endY = endY < 0 ? 0 : endY > maxY ? maxY : endY;
//开始移动
setX(endX);
setY(endY);
//记录位置
mLastRawX = mRawX;
mLastRawY = mRawY;
} else {
isScroll = false;
}
break;
case MotionEvent.ACTION_UP://手指离开
Log.d("LUO", "isDrug========" + isDrug);
//判断是否为点击事件
float center = mRootMeasuredWidth / 2;
//自动贴边
if (mLastRawX <= center) {
//向左贴边
AttachButton.this.animate()
.setInterpolator(new BounceInterpolator())
.setDuration(500)
.x(0)
.start();
left = true;
rl_attach.setBackground(BaseApplication.getInstance().getDrawable(R.drawable.class_left_round_4));
} else {
//向右贴边
AttachButton.this.animate()
.setInterpolator(new BounceInterpolator())
.setDuration(500)
.x(mRootMeasuredWidth - getWidth())
.start();
left = false;
rl_attach.setBackground(BaseApplication.getInstance().getDrawable(R.drawable.class_right_round_4));
}
Log.d("LUO", "是否滑动=====isScroll=" + isScroll);
if (!isScroll) {
if (onCountDownLister != null) {
//取消倒计时
onCountDownLister.OnCountDown(left);
rl_attach.setVisibility(GONE);
}
}
break;
}
//是否拦截事件
return true;
}
/**
* 设置倒计时
*
* @param second
*/
public void setCountDown(int second) {
rl_attach.setVisibility(VISIBLE);
if (second > 0) {
iv_alarm.setVisibility(GONE);
tv_alarm.setVisibility(VISIBLE);
tv_alarm.setText(String.valueOf(second) + "秒");
//取消倒计时
cancelDownTime();
countDown(second * 1000 + 200);
}
}
/**
* 倒计时显示
*
* @param second
*/
CountDownTimer timer;
private void countDown(int second) {
timer = new CountDownTimer(second, 1000) {
@Override
public void onTick(long millisUntilFinished) {
//每秒执行一次
int value = (int) (millisUntilFinished / 1000);
tv_alarm.setText(value + "秒");
}
@Override
public void onFinish() {
// 最后执行完操作
//取消倒计时
cancelDownTime();
rl_attach.setVisibility(VISIBLE);
iv_alarm.setVisibility(VISIBLE);
tv_alarm.setVisibility(GONE);
}
}.start();
}
/**
* 取消倒计时
*/
private void cancelDownTime() {
if (timer != null) {
timer.cancel();
timer = null;
}
}
/**
* 倒计时按钮
*
* @param onCountDownLister
*/
public void setOnButtonListener(OnCountDownLister onCountDownLister) {
this.onCountDownLister = onCountDownLister;
}
/**
* Java 接口
*/
public interface OnCountDownLister {
void OnCountDown(boolean left);
}
}
2、attach_button.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rl_attach"
android:layout_width="@dimen/dp_54"
android:layout_height="@dimen/dp_54"
android:background="@drawable/class_right_round_4">
<ImageView
android:id="@+id/iv_alarm"
android:visibility="visible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/class_alarm"
android:layout_centerInParent="true"
/>
<TextView
android:id="@+id/tv_alarm"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
tools:text="60秒"
android:textSize="@dimen/sp_14"
android:padding="@dimen/dp_8"
android:textColor="@color/color_FFFFFF"
/>
</RelativeLayout>
3、自定义view的使用
<com.sumansoul.teacher.utils.view.AttachButton
android:id="@+id/attach"
android:layout_width="@dimen/dp_54"
android:layout_height="@dimen/dp_54"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_marginBottom="@dimen/dp_71" />
4、styles布局中
<declare-styleable name="AttachButton">
<!--是否需要自动吸边-->
<attr name="customIsAttach" format="boolean" />
<!--是否可拖曳-->
<attr name="customIsDrag" format="boolean" />
</declare-styleable>
5、class_center_round_4
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- 外层边框1 -->
<item>
<shape>
<padding
android:bottom="@dimen/dp_1_2"
android:left="@dimen/dp_1_2"
android:right="@dimen/dp_1_2"
android:top="@dimen/dp_1_2" />
<solid android:color="@color/color_FF535461" />
<corners
android:topRightRadius="@dimen/dp_27"
android:bottomRightRadius="@dimen/dp_27"
android:topLeftRadius="@dimen/dp_27"
android:bottomLeftRadius="@dimen/dp_27"/>
</shape>
</item>
<!-- 边框颜色值 -->
<item>
<shape>
<padding
android:bottom="@dimen/dp_1"
android:left="@dimen/dp_1"
android:right="@dimen/dp_1"
android:top="@dimen/dp_1" />
<solid android:color="@color/color_66000000" />
<size android:width="@dimen/dp_52"
android:height="@dimen/dp_52"/>
<corners
android:topLeftRadius="@dimen/dp_26"
android:bottomLeftRadius="@dimen/dp_26"
android:topRightRadius="@dimen/dp_26"
android:bottomRightRadius="@dimen/dp_26"
/>
</shape>
</item>
<!-- 主体背景颜色值 -->
<item>
<!-- 边框里面背景颜色 白色 -->
<shape>
<solid android:color="@color/color_66000000" />
<size android:width="@dimen/dp_52" android:height="@dimen/dp_52"/>
<corners
android:topLeftRadius="@dimen/dp_26"
android:bottomLeftRadius="@dimen/dp_26"
android:topRightRadius="@dimen/dp_26"
android:bottomRightRadius="@dimen/dp_26"
/>
</shape>
</item>
</layer-list>
6、class_left_round_4
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<!--外层边框1 -->
<item>
<shape>
<padding
android:bottom="@dimen/dp_1_2"
android:left="0dp"
android:right="@dimen/dp_1_2"
android:top="@dimen/dp_1_2" />
<solid android:color="@color/color_FF535461" />
<corners
android:topRightRadius="@dimen/dp_27"
android:bottomRightRadius="@dimen/dp_27"
/>
</shape>
</item>
<!--外层边框2 -->
<item>
<shape>
<padding
android:bottom="@dimen/dp_1"
android:left="0dp"
android:right="@dimen/dp_1"
android:top="@dimen/dp_1" />
<solid android:color="@color/color_66000000" />
<corners
android:topRightRadius="@dimen/dp_26"
android:bottomRightRadius="@dimen/dp_26" />
</shape>
</item>
<!-- 主体背景颜色值 -->
<item>
<!-- 边框里面背景颜色 白色 -->
<shape>
<solid android:color="@color/color_66000000" />
<size android:width="@dimen/dp_52" android:height="@dimen/dp_52"/>
<corners
android:topRightRadius="@dimen/dp_26"
android:bottomRightRadius="@dimen/dp_26"
/>
</shape>
</item>
</layer-list>
7、class_right_round_4
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 外层边框1 -->
<item>
<shape>
<padding
android:bottom="@dimen/dp_1_2"
android:left="@dimen/dp_1_2"
android:right="0dp"
android:top="@dimen/dp_1_2" />
<solid android:color="@color/color_FF535461" />
<corners
android:bottomLeftRadius="@dimen/dp_27"
android:topLeftRadius="@dimen/dp_27" />
</shape>
</item>
<!-- 外层边框2 #535461-->
<item>
<shape>
<padding
android:bottom="@dimen/dp_1"
android:left="@dimen/dp_1"
android:right="0dp"
android:top="@dimen/dp_1" />
<solid android:color="@color/color_66000000" />
<corners
android:bottomLeftRadius="@dimen/dp_26"
android:topLeftRadius="@dimen/dp_26" />
</shape>
</item>
<!-- 主体背景颜色值 -->
<item>
<!-- 边框里面背景颜色 白色 -->
<shape>
<solid android:color="@color/color_66000000" />
<size
android:width="@dimen/dp_52"
android:height="@dimen/dp_52" />
<corners
android:bottomLeftRadius="@dimen/dp_26"
android:topLeftRadius="@dimen/dp_26" />
</shape>
</item>
</layer-list>
7、点击事件
/**
* 点击了倒计时
*/
attach.setOnButtonListener(new AttachButton.OnCountDownLister() {
@Override
public void OnCountDown(boolean left) {
//点击事件
}
});
8、注意:
我在写腾讯直播上课时,用了两个组合控件,用同一个AttachButton 在布局引用两次,出现第二个可以正常使用,写两个AttachButton 就可以了。
网友评论