26
2017
09

自定义可点击和滑动的按钮

一. 在最近的项目中需要实现一个这样的自定义View,有点类似一个开关选择按钮,可以点击、可以滑动。在按钮下方是一个ViewPager,根据按钮的切换,同时切换ViewPager的界面。同时滑动ViewPager,也会切换到按钮不同的登录方式。把实现步骤记录一下,方便以后扩展和复用,同时加深一下对自定义View的理解。

总结:主要解决的问题是 1. 自定义View的步骤 , 2. 点击和滑动的冲突 2. 自定义控件和ViewPager的联动切换。
问题: 想实现点击的时候滑块也可以向滑动按钮一样平滑的滑动到另一边,并且让下面的ViewPager也匀速滑动,有什么好的方法处理

这里写图片描述

二. 看下面的几个gif图的演示效果。

  • 滑动上面的控件,让ViewPager中的Fragment进行切换。
    这里写图片描述
  • 滑动控件下方的ViewPager让按钮的滑块进行同向移动(gif文件过大,无法上传)

  • 点击控件,切换不同的按钮背景
    这里写图片描述

三. 实现上面三个效果的步骤:
1. 新建一个SwitchButton类继承自View, 实现View的构造方法,测量,绘制的方法,并在xml中引用该控件。
构造方法:

public SwitchButton(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

initView方法初始化需要绘制的Bitmap和画笔、以及绘制的一些位置参数
login_select_liner_bg是整个控件的大的背景图片
这里写图片描述
要滑动的选择器login_selected_text_bg
这里写图片描述

通过BitmapFactory.decodeResource把drawable文件夹下的png图片转换成Bitmap图像。

backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.login_select_liner_bg);

slidingBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.login_selected_text_bg);

初始化绘制的时候是:slidingBitmap 在 backgroundBitmap的靠左居中, 让slidingBitmap 距离top为slidingMarginTop 。

slidingMarginTop = (backgroundBitmap.getHeight() - slidingBitmap.getHeight()) /2;

上面Bitmap的绘制需要画笔,初始化一个画笔

mBitMapPaint = new Paint();
mBitMapPaint.setAntiAlias(true);

控件上的文字也是绘制上去的,同样需要画笔,黄色文字和白色文字的画笔设置。

        mDefaultTextPaint = new Paint();
        mDefaultTextPaint.setAntiAlias(true);
        mDefaultTextPaint.setTextSize(32);
        mDefaultTextPaint.setStyle(Paint.Style.FILL);
        mDefaultTextPaint.setTextAlign(Paint.Align.LEFT);
        mDefaultTextPaint.setColor(Color.WHITE);

        mSelectTextPaint = new Paint();
        mSelectTextPaint.setAntiAlias(true);
        mSelectTextPaint.setTextSize(32);
        mSelectTextPaint.setStyle(Paint.Style.FILL);
        mSelectTextPaint.setTextAlign(Paint.Align.LEFT);
        mSelectTextPaint.setColor(getResources().getColor(R.color.orange));
  1. 初始化参数之后,开始进行测量:这是一个view, 设置自定义View的宽高为backgroundBitmap的宽高
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(backgroundBitmap.getWidth(),                       backgroundBitmap.getHeight());
    }
  1. 开始绘制:
    slidingMarginLeft 表示滑动选择块离这个自定义view的左边偏移距离
    passwordLoginStr =“密码登录”
    smsLoginStr = “短信登录”
canvas.drawBitmap(backgroundBitmap,0 ,0, mBitMapPaint);
canvas.drawBitmap(slidingBitmap,slidingMarginLeft,slidingMarginTop, mBitMapPaint);

if (isSelectLeft) {
            canvas.drawText(passwordLoginStr, textLeftMargin, textVerticalLocation, mSelectTextPaint);
            canvas.drawText(smsLoginStr, slidMarginLeftMax + textLeftMargin, textVerticalLocation, mDefaultTextPaint);
        } else {
            canvas.drawText(passwordLoginStr, textLeftMargin, textVerticalLocation, mDefaultTextPaint);
            canvas.drawText(smsLoginStr, slidMarginLeftMax + textLeftMargin, textVerticalLocation, mSelectTextPaint);
        }
  1. 添加点击事件: 绘制出了view只后,只是如第一张图片一样是一个静态的效果,需要添加点击事件。
    让View继承自View.OnClickListener 实现onClick方法:
    默认当滑到左边的时候slidingBitmap 距离左边为5个像素(把像素装换成dp会有更好的适配效果),滑到右边的时候距离左边最大的距离slidMarginLeftMax = backgroundBitmap.getWidth() - slidingBitmap.getWidth() - 5(把像素装换成dp会有更好的适配)

isSelectLeft 表示是否点击选中左边的密码登录,默认为true,每点击一次进行切换,上面的文字绘制需要根据这个值进行判断。

//实现控件的点击事件
    @Override
    public void onClick(View v) {
            isSelectLeft = !isSelectLeft;
            if (isSelectLeft) {
                slidingMarginLeft = 5;
            } else {
                slidingMarginLeft = slidMarginLeftMax;
            }
            //isChangeComplete = true;
            invalidate();
    }

在 initView 中 加上setOnClickListener(this);
这样就能相应点击事件了。

  1. 除了点击效果外,还需要添加滑动处理:实现onTouchEvent方法
 @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                //计算按下的坐标
                isStartX = startX = event.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                float endX = event.getX();
                float distanceX = endX - startX;
                slidingMarginLeft += distanceX;
                //防止将滑块滑出界面,屏蔽非法值
                if(slidingMarginLeft > slidMarginLeftMax){
                    slidingMarginLeft = slidMarginLeftMax;
                }else if(slidingMarginLeft < 5){
                    slidingMarginLeft = 5;
                }
                invalidate();
                //数据还原
                startX = event.getX();
                break;
            case MotionEvent.ACTION_UP:
                    if (slidingMarginLeft > slidMarginLeftMax / 2) {
                        slidingMarginLeft = slidMarginLeftMax;
                        isSelectLeft = false;
                    } else {
                        slidingMarginLeft = 5;
                        isSelectLeft = true;
                    }
                    invalidate();
                break;
        }
        return true;
    }
  1. 在添加了点击和触摸滑动效果之后会出现冲突效果,解决冲突的办法是,声明一个boolean值的变量isEnableClick,如果boolean 为true表示点击事件生效,当活动触摸是,将其设置为false, 表示点击事件不生效。
    修改之后的点击事件onClick方法:
@Override
    public void onClick(View v) {
        if(isEnableClick) {
            isSelectLeft = !isSelectLeft;
            if (isSelectLeft) {
                slidingMarginLeft = 5;
            } else {
                slidingMarginLeft = slidMarginLeftMax;
            }
            //isChangeComplete = true;
            invalidate();
        }
    }

触摸事件 onTouchEvent也做相应的修改。

@Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                //计算按下的坐标
                isStartX = startX = event.getX();
                isEnableClick = true;
                break;
            case MotionEvent.ACTION_MOVE:
                float endX = event.getX();
                float distanceX = endX - startX;
                slidingMarginLeft += distanceX;
                //屏蔽非法值
                if(slidingMarginLeft > slidMarginLeftMax){
                    slidingMarginLeft = slidMarginLeftMax;
                }else if(slidingMarginLeft < 5){
                    slidingMarginLeft = 5;
                }
                //isChangeComplete = false;
                invalidate();
                //数据还原
                startX = event.getX();

                if(Math.abs(endX - isStartX) > 5){
                    //已经滑动了 就不再执行点击事件
                    isEnableClick = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                if(!isEnableClick) {
                    if (slidingMarginLeft > slidMarginLeftMax / 2) {
                        slidingMarginLeft = slidMarginLeftMax;
                        isSelectLeft = false;
                    } else {
                        slidingMarginLeft = 5;
                        isSelectLeft = true;
                    }
                    invalidate();
                }
                break;
        }
        return true;
    }
  1. 实现与ViewPager的联动方式一:点击按钮切换ViewPager
    在View中第一个点击监听的interface
 public   interface   ChangeSelectListener{
       void changeSelect(boolean isSelectLeft);
    }

    public void setChangeSelectListener(ChangeSelectListener changeSelectListener){
        mChangeSelectListener = changeSelectListener;
    }

在onClick方法中加上回调方法

if(mChangeSelectListener != null && isEnableClick) {
                mChangeSelectListener.changeSelect(isSelectLeft);
            }
  1. XXActivity的xml中定义如下
 <LinearLayout
        android:id="@+id/login_model_switch_liner"
        android:layout_width="wrap_content"
        android:orientation="horizontal"
        android:layout_height="0dp"
        android:layout_weight="1.5"
        android:gravity="center_vertical">

        <kap.com.smarthome.android.ui.view.SwitchButton
            android:id="@+id/switchButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="@dimen/margin_20"
            />

    </LinearLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/login_view_pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginTop="15dp"
        android:layout_weight="9.5">
    </android.support.v4.view.ViewPager>

在 XXActivity中ViewPager的处理代码:

 //设置ViewPager的适配器, 设置展示的fragment
        mMethodViewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
            @Override
            public Fragment getItem(int position) {
                return fragments.get(position);
            }

            @Override
            public int getCount() {
                return fragments.size();
            }
        });

        //给ViewPager添加切换的监听器
        mMethodViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
//滑动ViewPager的时候让mSwitchButton的滑块跟着按照比例跟着滑动, //setSlidingMarginLeft的方法下面给出
          mSwitchButton.setSlidingMarginLeft(positionOffset);
            }
            @Override
            public void onPageSelected(int position) {
                //当滑动结束之后,设置是滑动到左边还是右半边 
                //SwitchButton中的setSelectLeft方法后面给出
                if(position == 0){
                    mSwitchButton.setSelectLeft(true);
                }else{
                    mSwitchButton.setSelectLeft(false);
                }
            }
            @Override
            public void onPageScrollStateChanged(int state) {}
        });


        mListener = new SwitchButton.ChangeSelectListener() {
            @Override
            public void changeSelect(boolean isSelectLeft) {
                if(isSelectLeft){
                    mMethodViewPager.setCurrentItem(0);
                }else{
                    mMethodViewPager.setCurrentItem(1);
                }
            }
        };

        mSwitchButton.setChangeSelectListener(mListener);
  1. SwitchButton类中的setSlidingMarginLeft方法 传入的scalex是viewpager的滑动比例,根据这个值可以计算出当前slidingMarginLeft 的值
public void setSlidingMarginLeft(float scaleX){
        if(scaleX != 0) {
            slidingMarginLeft = (int) (slidMarginLeftMax * scaleX);
            invalidate();
        }
    }
  1. SwitchButton类中的setSelectLeft方法, 表示ViewPager滑动结束,slidingMarginLeft 重新设置最终值
public void setSelectLeft(boolean selectLeft) {
        isSelectLeft = selectLeft;
        if (isSelectLeft) {
            slidingMarginLeft = 5;
        } else {
            slidingMarginLeft = slidMarginLeftMax;
        }
        invalidate();
    }

下面给出SwitchButton类的完整代码, ViewPager的代码在上面第8部分已经基本给出。

/** * Created by Administrator on 2017/9/22 0022. * * 创建自定义View用于登录界面中密码登录和短信登录的切换 * * 1.构造方法,实例化类 * 2.开始测量 measure(int , int) -> onMeasure(); * 如果当前view是一个ViewGroup,还有义务测量自己的孩子,孩子有建议权 * 3. 指定位置-layout()--> onlayout(); * 指定控件的位置,一般view不用写这个方法,ViewGroup的时候才需要,一般View不需要重写该方法 * 4. 绘制视图 -- draw() --> onDraw(Canvas) * 根据上面两个方法的参数,进行绘制 */

public class SwitchButton extends View implements View.OnClickListener{

    private Bitmap backgroundBitmap;
    private Bitmap slidingBitmap;

    private int slidMarginLeftMax;
    private Paint mBitMapPaint;
    private Paint mDefaultTextPaint;
    private Paint mSelectTextPaint;

    private int slidingMarginTop;
    private int slidingMarginLeft = 5;
    private int textVerticalLocation;
    private int textLeftMargin = 20;

    //是否选择左边
    private boolean isSelectLeft = true;

    //默认点击事件生效 ,滑动事件不生效
    private boolean isEnableClick = true;

    //横向滑动的X轴起点坐标
    private float startX;
    private float isStartX;

    private String passwordLoginStr;
    private String smsLoginStr;


    private ChangeSelectListener  mChangeSelectListener;

    //判断与之监听的 ViewPager 是否滑动结束 , 只有当滑动结束的时候文字才变化。
    /*private boolean isChangeComplete = false ; private boolean isFirstDraw = true;*/

    public SwitchButton(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    private void initView(){
        backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.login_select_liner_bg);
        slidingBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.login_selected_text_bg);

        passwordLoginStr = getResources().getString(R.string.password_login);
        smsLoginStr = getResources().getString(R.string.sms_login);

        slidMarginLeftMax = backgroundBitmap.getWidth() - slidingBitmap.getWidth() - 5;
        slidingMarginTop = (backgroundBitmap.getHeight() - slidingBitmap.getHeight()) /2 - 2;
        textVerticalLocation = backgroundBitmap.getHeight()/2+5;

        mBitMapPaint = new Paint();
        mBitMapPaint.setAntiAlias(true);

        mDefaultTextPaint = new Paint();
        mDefaultTextPaint.setAntiAlias(true);
        mDefaultTextPaint.setTextSize(32);
        mDefaultTextPaint.setStyle(Paint.Style.FILL);
        mDefaultTextPaint.setTextAlign(Paint.Align.LEFT);
        mDefaultTextPaint.setColor(Color.WHITE);

        mSelectTextPaint = new Paint();
        mSelectTextPaint.setAntiAlias(true);
        mSelectTextPaint.setTextSize(32);
        mSelectTextPaint.setStyle(Paint.Style.FILL);
        mSelectTextPaint.setTextAlign(Paint.Align.LEFT);
        mSelectTextPaint.setColor(getResources().getColor(R.color.orange));

        setOnClickListener(this);
    }

    /** * 测量这个view的大小 * @param widthMeasureSpec * @param heightMeasureSpec */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight());
    }

    /** * 进行绘制 * @param canvas */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawBitmap(backgroundBitmap,0 ,0, mBitMapPaint);
        canvas.drawBitmap(slidingBitmap,slidingMarginLeft,slidingMarginTop, mBitMapPaint);

        drawText(canvas);
    }

    /** * 绘制文本 * @param canvas */
    private void drawText(Canvas canvas) {
        if (isSelectLeft) {
            canvas.drawText(passwordLoginStr, textLeftMargin, textVerticalLocation, mSelectTextPaint);
            canvas.drawText(smsLoginStr, slidMarginLeftMax + textLeftMargin, textVerticalLocation, mDefaultTextPaint);
        } else {
            canvas.drawText(passwordLoginStr, textLeftMargin, textVerticalLocation, mDefaultTextPaint);
            canvas.drawText(smsLoginStr, slidMarginLeftMax + textLeftMargin, textVerticalLocation, mSelectTextPaint);
        }
    }


    //实现控件的点击事件
    @Override
    public void onClick(View v) {
        if(isEnableClick) {
            isSelectLeft = !isSelectLeft;
            if (isSelectLeft) {
                slidingMarginLeft = 5;
            } else {
                slidingMarginLeft = slidMarginLeftMax;
            }

            if(mChangeSelectListener != null && isEnableClick) {
                mChangeSelectListener.changeSelect(isSelectLeft);
            }

            invalidate();
        }
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                //计算按下的坐标
                isStartX = startX = event.getX();
                isEnableClick = true;
                break;
            case MotionEvent.ACTION_MOVE:
                float endX = event.getX();
                float distanceX = endX - startX;
                slidingMarginLeft += distanceX;
                //屏蔽非法值
                if(slidingMarginLeft > slidMarginLeftMax){
                    slidingMarginLeft = slidMarginLeftMax;
                }else if(slidingMarginLeft < 5){
                    slidingMarginLeft = 5;
                }
                //isChangeComplete = false;
                invalidate();
                //数据还原
                startX = event.getX();

                if(Math.abs(endX - isStartX) > 5){
                    //已经滑动了 就不再执行点击事件
                    isEnableClick = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                if(!isEnableClick) {
                    if (slidingMarginLeft > slidMarginLeftMax / 2) {
                        slidingMarginLeft = slidMarginLeftMax;
                        isSelectLeft = false;
                    } else {
                        slidingMarginLeft = 5;
                        isSelectLeft = true;
                    }
                    invalidate();
                }
                break;
        }
        return true;
    }



    public boolean isSelectLeft() {
        return isSelectLeft;
    }

    public void setSelectLeft(boolean selectLeft) {
        isSelectLeft = selectLeft;
        if (isSelectLeft) {
            slidingMarginLeft = 5;
        } else {
            slidingMarginLeft = slidMarginLeftMax;
        }
        invalidate();
    }


    public void setSlidingMarginLeft(float scaleX){
        if(scaleX != 0) {
            slidingMarginLeft = (int) (slidMarginLeftMax * scaleX);
            invalidate();
        }
    }

    public   interface ChangeSelectListener{
       void changeSelect(boolean isSelectLeft);
    }

    public void setChangeSelectListener(ChangeSelectListener changeSelectListener){
        mChangeSelectListener = changeSelectListener;
    }
}
上一篇:Android Native内存泄漏诊断 下一篇:Photon_创建工程添加MySql.data库_001