高级UI-自定义控件

自定义控件在Android开发中有着大量的运用,为了做出符合项目的效果很多时候需要自定义控件,这里就使用两个自定义控件,来说明自定义控件的使用流程

仿QQ侧滑

之前使用DrawerLayoutNavigationView都实现了侧滑的效果,在这里使用自定义的View完成相同的效果
这里考虑到的是继承HorizontalScrollView,复写里面的onMeasure方法,设置滑动菜单和主菜单的宽度设置,复写onLayout方法,按照需求摆放子控件,复写onTouchEvent方法,控制移动距离,复写onScrollChanged方法,控制滑动时候的动画
布局

<?xml version="1.0" encoding="utf-8"?>
<com.cj5785.customviewtest.SlidingMenu xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/holo_orange_light"
    android:scrollbars="none">
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:orientation="horizontal">
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:background="@android:color/holo_green_light"/>
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:background="@android:color/darker_gray"/>
    </LinearLayout>

</com.cj5785.customviewtest.SlidingMenu>

自定义控件

public class SlidingMenu extends HorizontalScrollView {
    private int mScreenWidth;
    private ViewGroup mMenu;
    private ViewGroup mMain;
    private int mMenuWidth;
    private boolean isOnce;
    private float downX;

    public SlidingMenu(Context context, AttributeSet attrs) {
        super(context, attrs);
        //得到屏幕宽度
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics metrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(metrics);
        mScreenWidth = metrics.widthPixels;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //只测量一次
        if (!isOnce) {
            //获得侧滑菜单和主菜单
            LinearLayout wrapper = (LinearLayout) getChildAt(0);
            mMenu = (ViewGroup) wrapper.getChildAt(0);
            mMain = (ViewGroup) wrapper.getChildAt(1);
            //得到宽度,为了体验效果,这里一般会设置menu的right padding宽度
            mMenuWidth = mScreenWidth - mScreenWidth / 5;
            mMenu.getLayoutParams().width = mMenuWidth;
            mMain.getLayoutParams().width = mScreenWidth;
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (changed) {
            //开始绘制的时候,向右滑动一段距离
            this.scrollTo(mMenuWidth, 0);
            isOnce = true;
        }
        super.onLayout(changed, l, t, r, b);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                //按下时的位置
                downX = ev.getX();
                break;
            case MotionEvent.ACTION_UP:
                //松开后滑动的距离,滑动距离小于屏幕的四分之一则回到原位置
                float dx = ev.getX() - downX;
                if (dx < mScreenWidth / 4) {
                    this.smoothScrollTo(mMenuWidth, 0);
                } else {
                    this.smoothScrollTo(0, 0);
                }
                return true;
        }
        return super.onTouchEvent(ev);
    }

    //当滑动开始时候会调用onScrollChanged()
    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        //在这里设置动画
        //滑动百分比
        float factor = (float) l / mMenuWidth;
        //1.平移效果
        mMenu.setTranslationX(mMenuWidth * factor * 0.7F);
        //2.缩放效果
        mMenu.setScaleX(1 - 0.4F * factor);
        mMenu.setScaleY(1 - 0.4F * factor);
        mMain.setScaleX(0.9F + 0.1F * factor);
        mMain.setScaleY(0.9F + 0.1F * factor);
        //3.透明度效果
        mMenu.setAlpha(1 - factor);
        mMain.setAlpha(0.8F + 0.2F * factor);
        super.onScrollChanged(l, t, oldl, oldt);
    }
}

实现效果如下
自定义控件-仿QQ侧滑

实现条目侧滑的效果

在QQ中,还有个功能是比较棒的,那就是条目侧滑,那么要怎么实现条目侧滑效果呢
这里我们使用自定义LinearLayout来实现,其滑动效果通过Scroller来控制
使用dispatchTouchEvent()来记录DOWN,MOVE,UP事件响应的参数,滑动过程中不断调用computeScroll()移动控件位置,使得其形成动画效果,由于要控制滑动距离,就需要动态测量出滑出的距离,复写onFinishInflate()得到距离
布局很简单,就是两个TextView

<?xml version="1.0" encoding="utf-8"?>
<com.cj5785.customviewtest.SlidingItemMenuLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:background="@android:color/holo_green_light"
        android:gravity="center"
        android:text="This is a item"
        android:textSize="32sp" />

    <TextView
        android:layout_width="100dp"
        android:layout_height="80dp"
        android:background="@android:color/holo_red_light"
        android:gravity="center"
        android:text="删除"
        android:textSize="32sp" />

</com.cj5785.customviewtest.SlidingItemMenuLayout>

然后是自定义控件

public class SlidingItemMenuLayout extends LinearLayout {
    private Scroller mScroller;
    private float startX;
    private float startY;
    private float dx;
    private float dy;
    private View rightChild;

    public SlidingItemMenuLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setOrientation(LinearLayout.HORIZONTAL);
        //用来设置松开回弹
        mScroller = new Scroller(getContext(), new AccelerateInterpolator(), true);
    }

	//绘制完成后调用
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        rightChild = getChildAt(1);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //记录按下时位置
                startX = ev.getX();
                startY = ev.getY();
                //按下以后拦截事件,交由父类处理
                super.dispatchTouchEvent(ev);
                return true;
            case MotionEvent.ACTION_MOVE:
                dx = ev.getX() - startX;
                dy = ev.getY() - startY;
                //系统能检测到的最小距离
                if (Math.abs(dx) - Math.abs(dy) > ViewConfiguration.getTouchSlop()) {
                    //向左滑动距离不能大于最右边,且向右滑动距离不能大于零
                    if (getScrollX() + (-dx) > rightChild.getWidth()
                            || getScrollX() + (-dx) < 0) {
                        return true;
                    }
                    //滑动一段距离,重新记录位置,拦截滑动事件
                    scrollBy((int) -dx, 0);
                    startX = ev.getX();
                    startY = ev.getY();
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
                //得到松手时的偏移量,超过一半则全部显示,否则不显示
                int offset = getScrollX() / (float) rightChild.getWidth() > 0.5 ?
                        rightChild.getWidth() - getScrollX() : -getScrollX();
                //初始化滑动参数
                mScroller.startScroll(getScrollX(), getScrollY(), offset, 0);
                //重新绘制测量,此时会调用computeScroll()方法
                invalidate();
                //参数重新赋值
                startX = 0;
                startY = 0;
                dx = 0;
                dy = 0;
                break;
            default:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    //开启滑动以后,就会不断调用此方法
    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
        super.computeScroll();
    }
}

实现效果如下
自定义控件-Item侧滑

posted @ 2019-04-06 23:16  cj5785  阅读(274)  评论(0编辑  收藏  举报