View的滑动与弹性滑动(改变View的参数+属性动画)

自定义控件系列:
秒懂OnMeasure
秒懂OnLayout
让自定义ViewGroup里的子控件支持Margin
让自定义ViewGroup支持Padding
自定义ViewGroup的一个综合实践 FlowLayout
onDraw
最简单的自定义View:SwitchView

这次做一个SwitchView,需要使用下面的几个知识点:

知识点:

自定义View不是每次都要重写onMeasure和onLayout,为了避免手写onMeasure和onLayout,不要直接继承ViewGroup,而是继承LinearLayout、FrameLayout、RelativeLayout这种已经重写过了onMeasure和onLayout的布局

  1. 如何获取控件的宽高

    我们知道经常因为控件还没加载出来就去获取宽高,那么得到的宽高为0,使用view.post()是获取宽高最简单的方式之一(为什么呢,看了源码后,巴拉巴拉…),当然还有其他很多方法。

    post(new Runnable() {
                @Override
                public void run() {
                    mWidth = getMeasuredWidth();
                    mHeight = getMeasuredHeight();
                }
            });
    
  2. 如何动态地添加一个子控件

    我们经常需要通过代码来添加子控件,而不是写到xml里,当然是addView了
    addView(View child) 把子控件添加到末尾
    void addView(View child, int index)把子控件添加到指定位置
    addView(View child, int width, int height)把子控件添加到末尾,并为子控件指定宽高
    addView(View child, LayoutParams params)把子控件添加到末尾,子控件的宽高、margin、gravity等等所有xml里以“layout_xxx"开头的属性全部封装在LayoutParams里
    addView(View child, int index, LayoutParams params)把子控件添加到指定位置,子控件的宽高、margin、gravity等等所有xml里以“layout_xxx"开头的属性全部封装在LayoutParams里
    上面的方法都是间接或直接调用了这个方法

    可见LayoutParams也很重要

    1. 不同的ViewGroup都有自己专属的LayoutParams,对应xml里“layout_xxx"的所有属性,我们如果想使用这些属性,必须通过设置子控件的LayoutParams来实现
      下图是LinearLayout的Layout_开头的属性在这里插入图片描述

    下图是RelativeLayut以Layout_开头的属性

    在这里插入图片描述

    举个例子:在RelativeLayout里动态添加子控件

     		rl = (RelativeLayout) findViewById(R.id.rl);
            
            TextView textView = new TextView(this);
            textView.setText("我是一个textview");
            textView.setBackgroundColor(Color.RED);
            textView.setId(R.id.textview);//动态设置id,见https://www.cnblogs.com/codingblock/p/5090441.html
            RelativeLayout.LayoutParams lp1 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            lp1.leftMargin = 10;
            lp1.setMargins(100, 10, 10, 10);
            lp1.addRule(RelativeLayout.CENTER_IN_PARENT);
            textView.setLayoutParams(lp1);
            rl.addView(textView);
    
    
            ImageView imageView = new ImageView(this);
            imageView.setImageResource(R.mipmap.ic_launcher);
            RelativeLayout.LayoutParams lp2 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            //★注意:这里new的是RelativeLayout.LayoutParams,因为你在为RelativeLayout添加子控件
            lp2.addRule(RelativeLayout.BELOW, R.id.textview);
            rl.addView(imageView, lp2);
    
    

    在这里插入图片描述

  3. 重写onTouchEvent,返回true,表示当前的view要处理事件(事件分发会详细说这个方法)

  4. 如何让view滑动起来

    1. 通过改变一个view的LayoutParams属性值(这里使用了margin),来实现改变view的位置
    2. 还可以通过scrollBy来实现,下篇博客举这个例子
  5. 如何实现弹性滑动,即松手后view自己平滑地移动

    1. 属性动画逐渐地改变一个属性,达到平滑移动效果
    2. scroller可以实现平滑移动

应用

效果

一个开关,滑块随着手指滑动,松手后,判断滑动位置是不是过半,过半的话平滑移动到最右侧,没过半平滑移动到最左侧
在这里插入图片描述

代码

package com.view.custom.dosometest.view;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ImageView;
import android.widget.LinearLayout;

/**
 * 描述当前版本功能
 *
 * @Project: DoSomeTest
 * @author: cjx
 * @date: 2019-12-01 10:06  星期日
 */
public class SwitchView extends LinearLayout {


    private ImageView mImageView;
    private LayoutParams mLayoutParams;
    private int mSliderHeight;
    private int mSliderWidth;
    private int mWidth;
    private int mHeight;

    public SwitchView(Context context) {
        super(context);
        init(context);
    }

    public SwitchView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

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


    private void init(Context context) {
        setBackgroundColor(Color.GRAY);

        post(new Runnable() {
            @Override
            public void run() {
                addSlider();//因为这里需要用到控件的宽高,所以写到post里
            }
        });

    }

    private void addSlider() {
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();

        mSliderWidth = mWidth / 2;
        mSliderHeight = mHeight;

		// 注意导包LayoutParams是LinearLayout.LayoutParams
        mLayoutParams = new LayoutParams(mSliderWidth, mSliderHeight);
        mImageView = new ImageView(getContext());
        mImageView.setBackgroundColor(Color.CYAN);
        mImageView.setLayoutParams(mLayoutParams);
        addView(mImageView);
    }


    float mLastX;

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        float x = event.getX();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:

                mLastX = x;
                break;

            case MotionEvent.ACTION_MOVE:
                float deltaX = x - mLastX;

                // 防止滑块滑出边界,矫正deltaX
                if (mLayoutParams.leftMargin + mSliderWidth + deltaX > mWidth) {
                    deltaX = mWidth - mLayoutParams.leftMargin - mSliderWidth;
                } else if (mLayoutParams.leftMargin + deltaX < 0) {
                    deltaX = -mLayoutParams.leftMargin;
                }
				// 动态改变这个控件的左边距,实现控件的滑动
                mLayoutParams.leftMargin += (int) deltaX;
                //别忘了通过setLayoutParams,使你的改动生效
                mImageView.setLayoutParams(mLayoutParams);
                mLastX = x;
                break;


            case MotionEvent.ACTION_UP:
				// 松手后,通过属性动画改变margin实现控件的平滑移动
                if (mLayoutParams.leftMargin > mSliderWidth / 2) {
                    smoothChangeLeftMargin(mLayoutParams.leftMargin, mSliderWidth);

                } else {
                    smoothChangeLeftMargin(mLayoutParams.leftMargin, 0);

                }

                break;
        }
        return true;
    }

    /**
     * 使用属性动画平滑地过度leftMargin
     * @param start
     * @param end
     */
    private void smoothChangeLeftMargin(int start, int end) {
        ValueAnimator valueAnimator = ValueAnimator.ofInt(start, end);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mLayoutParams.leftMargin = (int) animation.getAnimatedValue();
                mImageView.setLayoutParams(mLayoutParams);

            }
        });
        valueAnimator.setDuration(300);
        valueAnimator.start();

    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章