安卓 真 自定义SwitchButton
前言
switch控件在安卓里面是一个挺常用的控件,用来直白的表示一个状态的两面性。
它本身可以自定义的东西有track和thumb以及background,这些当然可以都放入selector样式里,但是如果我们要实现如下的效果
假如用安卓自带的switch来实现的话thumb可以用图片来代替,但是代表状态的小红点和小绿点就不能单靠资源文件来实现了。
过程
编写布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout android:id="@+id/card_checkview"
android:layout_width="55dp"
android:background="@drawable/switch_parent_shap"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:layout_marginLeft="5dp"
android:background="@drawable/can_exqute"
android:layout_width="6dp"
android:layout_centerVertical="true"
android:layout_height="6dp"/>
<ImageView
android:id="@+id/img_autowater_redpoint"
android:layout_marginLeft="5dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="@drawable/message_red"
android:layout_width="6dp"
android:layout_marginRight="5dp"
android:layout_height="6dp"/>
<LinearLayout
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:elevation="1dp"
android:background="@drawable/switch_shap"
android:id="@+id/card_openswitch"
android:layout_gravity="center_vertical"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="wrap_content">
<TextView
android:gravity="center"
android:layout_gravity="center"
android:textSize="8sp"
android:id="@+id/tv_switch_state"
android:layout_centerInParent="true"
android:layout_marginTop="1dp"
android:layout_marginBottom="1dp"
android:layout_marginLeft="7dp"
android:layout_marginRight="7dp"
android:textColor="@color/black"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
</LinearLayout>
</RelativeLayout>
</RelativeLayout>
自定义控件
public class SwitchCardView extends RelativeLayout implements View.OnClickListener {
ImageView imgAutowaterRedpoint;
RelativeLayout cardOpenswitch;
RelativeLayout cardCheckview;
TextView tvSwitchState;
private View view;
//(背景)父布局的宽度
private int parent_width;
//(track)子布局的宽度
private int child_width;
//开启的动画
private TranslateAnimation translateAnimation;
private TranslateAnimation close_translateAnimation;
private boolean initOpen = false;
private boolean open = false;
private boolean animate_open_start = false;
private boolean animate_close_start = false;
private String text_open = "已开启";
private String text_close = "关闭中";
private OnChangeListener onChangeListener;
//提供监听器给外部调用传出当前选中状态
public void setOnChangeListener(OnChangeListener onChangeListener) {
this.onChangeListener = onChangeListener;
}
public SwitchCardView(Context context) {
super(context);
init();
initLayoutGloab();
}
public void setTextopen(String text_open) {
this.text_open = text_open;
}
public void setTextclose(String text_close) {
this.text_close = text_close;
}
public SwitchCardView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
tvSwitchState.setText(text_open);
initLayoutGloab();
}
public SwitchCardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
initLayoutGloab();
}
public boolean isOpen() {
return open;
}
/* public void open(boolean open) {
this.open = open;
switchButton();
}*/
private void switchButton() {
if (open) {
//开启
translateAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
cardOpenswitch.setClickable(false);
}
@Override
public void onAnimationEnd(Animation animation) {
//结束后将view移动到指定位置
//代表平移动画结束了
cardOpenswitch.setClickable(true);
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) cardOpenswitch.getLayoutParams();
if (layoutParams.leftMargin == parent_width - child_width) {
//因为调用clearAnimation后会再次回调到该函数内部
Log.e("提示", "view的动画已经移动完成");
//已经设置成功了
return;
}
layoutParams.leftMargin = parent_width - child_width;
layoutParams.rightMargin = 0;
cardOpenswitch.clearAnimation();
cardOpenswitch.setLayoutParams(layoutParams);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
if (cardOpenswitch.getAnimation()==null)
{
cardOpenswitch.setAnimation(translateAnimation);
translateAnimation.start();
}
tvSwitchState.setText(text_open);
} else {
close_translateAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
cardOpenswitch.setClickable(false);
}
@Override
public void onAnimationEnd(Animation animation) {
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) cardOpenswitch.getLayoutParams();
//下面执行了AnimationClear之后会再走一次这里
cardOpenswitch.setClickable(true);
if (layoutParams.rightMargin == parent_width - child_width) {
//Log.e("提示", "view的动画已经移动完成");
//已经设置成功了
return;
}
layoutParams.rightMargin = parent_width - child_width;
layoutParams.leftMargin = 0;
cardOpenswitch.clearAnimation();
cardOpenswitch.setLayoutParams(layoutParams);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
if (cardOpenswitch.getAnimation()==null)
{
cardOpenswitch.setAnimation(close_translateAnimation);
close_translateAnimation.start();
}
tvSwitchState.setText(text_close);
}
}
private void initLayoutGloab() {
cardCheckview.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
if (parent_width != 0) {
return;
}
parent_width = cardCheckview.getWidth();
initAnimation();
initCloseAnimateon();
initopen();
});
cardOpenswitch.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
if (child_width != 0) {
//测量一次就够了
return;
}
child_width = cardOpenswitch.getWidth();
initAnimation();
initCloseAnimateon();
initopen();
});
cardOpenswitch.setOnClickListener(this);
}
private void initopen() {
int pos = 0;
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) cardOpenswitch.getLayoutParams();
if (parent_width != 0 && child_width != 0) {
pos = parent_width - child_width;
}
if (initOpen) {
layoutParams.leftMargin = pos;
layoutParams.rightMargin = 0;
}
else
{
layoutParams.rightMargin = pos;
layoutParams.leftMargin = 0;
}
cardOpenswitch.setLayoutParams(layoutParams);
}
public void setInitOpen(boolean initOpen) {
this.initOpen = initOpen;
if (initOpen)
{
tvSwitchState.setText(text_open);
}
else
{
tvSwitchState.setText(text_close);
}
//初始按钮赋值
open = initOpen;
}
private void init() {
//加载xml布局
view = LayoutInflater.from(getContext()).inflate(R.layout.switch_card_view, this);
imgAutowaterRedpoint = view.findViewById(R.id.img_autowater_redpoint);
cardCheckview = view.findViewById(R.id.card_checkview);
cardOpenswitch = view.findViewById(R.id.card_openswitch);
tvSwitchState = view.findViewById(R.id.tv_switch_state);
}
//关闭动画 往回移动
private Animation initCloseAnimateon() {
//在测量完成前将不初始化动画
if (parent_width == 0 || child_width == 0) {
return null;
}
close_translateAnimation = new TranslateAnimation(0, child_width - parent_width, 0, 0);//设置平移的起点和终点
close_translateAnimation.setDuration(500);//动画持续的时间为10s
close_translateAnimation.setFillEnabled(true);//使其可以填充效果从而不回到原地
close_translateAnimation.setFillAfter(true);//不回到起始位置
return close_translateAnimation;
}
private Animation initAnimation() {
if (parent_width == 0 || child_width == 0) {
return null;
}
//继续
translateAnimation = new TranslateAnimation(0, parent_width - child_width, 0, 0);//设置平移的起点和终点
translateAnimation.setDuration(500);//动画持续的时间为10s
translateAnimation.setFillEnabled(true);//使其可以填充效果从而不回到原地
translateAnimation.setFillAfter(true);//不回到起始位置
return translateAnimation;
}
@Override
public void onClick(View v) {
if (open)
{
open = false;
switchButton();
}
else
{
open = true;
switchButton();
}
if (null!=onChangeListener)
{
onChangeListener.onResult(open);
}
}
public interface OnChangeListener
{
void onResult(boolean checked);
}
}
运用的函数
1.已知安卓原switch的表现效果是一个平移动画所以我这里使用的是安卓的属性动画TranslateAnimation,并分别创建了
close_trasnslateAnimtaion 关闭状态的动画
open_trasnlateAnimation 开启状态的动画
2.使用viewTree的监听器来获得平移的距离
card_checkview 父控件(背景)
card_openswitch 子控件(tthumbview)
平移的距离是背景的宽度减去子控件的宽度然后再根据状态进行移动,布局只有在GlobalLayoutListener的回调里才能获得相对于当前展示的宽高数值。
private void initLayoutGloab() {
cardCheckview.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
//获取到背景宽
if (parent_width != 0) {
return;
}
parent_width = cardCheckview.getWidth();
initAnimation();
initCloseAnimateon();
initopen();
});
cardOpenswitch.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
//获取到thumb宽
if (child_width != 0) {
//测量一次就够了
return;
}
child_width = cardOpenswitch.getWidth();
initAnimation();
initCloseAnimateon();
initopen();
});
}
3.安卓属性动画的缺陷
动画完成后并不会将控件移动到动画完成后的位置,即使设置了setFitlerAfter为true后,对于事件的监听也在原位置所以需要在animation的结束动画的监听的地方手动移动控件的位置。
@Override
public void onAnimationEnd(Animation animation) {
//结束后将view移动到指定位置
//代表平移动画结束了
cardOpenswitch.setClickable(true);
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) cardOpenswitch.getLayoutParams();
if (layoutParams.leftMargin == parent_width - child_width) {
//因为调用clearAnimation后会再次回调到该函数内部
Log.e("提示", "view的动画已经移动完成");
//已经设置成功了
return;
}
layoutParams.leftMargin = parent_width - child_width;
layoutParams.rightMargin = 0;
cardOpenswitch.clearAnimation();
cardOpenswitch.setLayoutParams(layoutParams);
}
最后直接在xml里引用改该控件即可