Android自定义View之侧边栏初探

来源:互联网 发布:大数据 涂子沛在线阅读 编辑:程序博客网 时间:2024/06/07 20:27

前面写了几篇关于进程/线程原理的;感觉和应用层关系不大;但是实际上,写代码和做事情一样,都注重的是一个严谨的思维习惯;如果你真的深刻的理解了底层进程/线程交互模型,内存管理等;那么对你做APP也是有很大帮助的;比如为进程分配资源的时候,系统的一套思路就很值得借鉴;就能够避免应用中的内存问题;而管理多线程的思路更是值得在开发中借鉴。好了废话不多说;现在回到本章的主题,Android中从侧边栏的实现看自定义View;
首先我们知道,Android给应用层提供的组件有ViewFroup和VIew;其中ViewGroup是继承于View的;View相当于一个页面,但是ViewGroup可以容纳很多View;
现在分析下侧边栏的实现,侧边栏有2个页面,一个是菜单栏,一个是展示内容的页面;这两个页面都容纳在一个ViewGroup中;因此实际上侧边栏是由三个ViewGroup组成,背景是个ViewGroup,容纳了两个子ViewGroup,一个装载菜单,一个装载内容页面;
侧边栏有手势判断事件,这个可能是里面的难点;关于事件传递机制,可以参考
http://blog.csdn.net/jike0901xuye/article/details/47616051这一篇;然后侧边栏还有一个是平移效果,这个估计也难倒了好多人;本篇博客会给你一个通用的,不复杂的解决方案;
首先我们继承一个ViewGroup:定义所需要的对象;注意Scroller,这个就是我用来实现平移动画的类;

public class Sidebarview extends RelativeLayout {    /**     * @author 徐晔     * @note 侧边栏需要实现需要的配置文件     */    public static interface SidebarConfig {        /**边栏所占用的百分比*/        public int setSidebarpercent();        /**左边还是右边*/        public int setSidebarPosition();    }    /** 元素 */    private class SideElements {        /** 上下文*/        private Context mContext;        /** 屏幕宽度 */        private int screenwidth;        /** 菜单页面所在的页面*/        private RelativeLayout mMenuLayout;        /** 侧栏所在的页面 */        private RelativeLayout mSideLayout;        /** 配置文件 */        private SidebarConfig mConfig;        /**触摸时需要的临时文件 */        private int menuspace;        private int mMotionX, mdeteX;        public void setmMotionX(int mMotionX) {            this.mMotionX = mMotionX;        }    }    /** 元素*/    private SideElements mElements;    /** 平移动画实现类 */    private Scroller mScroller;    public void setElements(int setnumber) {        mElements.setmMotionX(setnumber);    }

然后实现必须实现的方法:

public Sidebarview(Context context, SidebarConfig sidebarConfig) {        super(context);        mElements = new SideElements();        initView(context, sidebarConfig);    }    public Sidebarview(Context context, AttributeSet attrs,            SidebarConfig sidebarConfig) {        super(context, attrs);        mElements = new SideElements();        initView(context, sidebarConfig);    }    public Sidebarview(Context context, AttributeSet attrs, int defStyle,            SidebarConfig sidebarConfig) {        super(context, attrs, defStyle);        mElements = new SideElements();        initView(context, sidebarConfig);    }

然后看重点1:将两个子ViewGroup添加进入:注意computeScroll方法,该方法在界面绘制的时候会调用;也就是只要界面有变动,就会调用该方法;所以该方法适合在平移时和用手拖动页面时,对别的View进行一个相关联的变化;

/** 初始化组件 */    private void initView(Context context, SidebarConfig sidebarConfig) {        mScroller = new Scroller(context);        //设置        mElements.mContext = context;        mElements.screenwidth = Utils.getScreenSize(context)[0];        mElements.mConfig = sidebarConfig;        this.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,                LayoutParams.MATCH_PARENT));        // 添加菜单页面        mElements.mMenuLayout = new RelativeLayout(context);        mElements.mMenuLayout.setId(0);        mElements.mMenuLayout.setTag("sidemenu");        LayoutParams mMenuParams = new LayoutParams(mElements.screenwidth                * ((100 - mElements.mConfig.setSidebarpercent())) / 100,                LayoutParams.MATCH_PARENT);        mElements.mMenuLayout.setLayoutParams(mMenuParams);        addView(mElements.mMenuLayout);        // 添加侧边栏页面        mElements.mSideLayout = new RelativeLayout(context) {            /** 平移动画 */            @Override            public void computeScroll() {                if (mScroller.computeScrollOffset()) {                    mElements.mSideLayout.scrollTo(mScroller.getCurrX(), 0);                    mElements.mSideLayout.postInvalidate();                }                if (mElements.mConfig.setSidebarPosition() % 2 == 0) {                    if (mElements.mSideLayout.getScrollX() == mElements.menuspace) {                        mElements.mMenuLayout.scrollTo(0, 0);                        return;                    }                    if (mElements.mSideLayout.getScrollX() == 0) {                        mElements.mMenuLayout.scrollTo(                                -mElements.menuspace >> 1, 0);                        return;                    }                    mElements.mMenuLayout.scrollTo(                            (-mElements.menuspace + mElements.mSideLayout                                    .getScrollX()) >> 1, 0);                }                if (mElements.mConfig.setSidebarPosition() % 2 == 1) {                    if (mElements.mSideLayout.getScrollX() == 0) {                        mElements.mMenuLayout.scrollTo(                                mElements.menuspace >> 1, 0);                        return;                    }                    if (mElements.mSideLayout.getScrollX() == mElements.menuspace) {                        mElements.mMenuLayout.scrollTo(0, 0);                        return;                    }                    mElements.mMenuLayout.scrollTo(                            (mElements.menuspace + mElements.mSideLayout                                    .getScrollX()) >> 1, 0);                }                super.computeScroll();            }        };        mElements.mSideLayout.setId(1);        mElements.mSideLayout.setTag("sideside");        LayoutParams mSideParams = new LayoutParams(LayoutParams.MATCH_PARENT,                LayoutParams.MATCH_PARENT);        mElements.mSideLayout.setLayoutParams(mSideParams);        addView(mElements.mSideLayout);        mElements.menuspace = mElements.screenwidth                * (100 - mElements.mConfig.setSidebarpercent()) / 100;    }

重点二:计算组件大小和摆放位置的代码:

    /** 控制组件的位置,根据前面设置的id来进行位置摆放*/    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        int childCount = getChildCount();        for (int i = 0; i < childCount; i++) {            View childView = getChildAt(i);            int measuredWidth = childView.getMeasuredWidth();            int measuredHeight = childView.getMeasuredHeight();            if (mElements.mConfig.setSidebarPosition() % 2 == 0) {                if (childView.getId() == 0) {                    childView.layout(                            mElements.screenwidth                                    * (mElements.mConfig.setSidebarpercent())                                    / 100, 0, mElements.screenwidth                                    * (mElements.mConfig.setSidebarpercent())                                    / 100 + measuredWidth, measuredHeight);                } else {                    childView.layout(0, 0, measuredWidth, measuredHeight);                }            }            if (mElements.mConfig.setSidebarPosition() % 2 == 1) {                childView.layout(0, 0, measuredWidth, measuredHeight);            }        }    }    /** 度量组件的大小*/    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        measureChildren(widthMeasureSpec, heightMeasureSpec);        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int heightSize = MeasureSpec.getSize(heightMeasureSpec);        setMeasuredDimension(widthSize, heightSize);    }

重点三:手势操作事件:

/** 右边滑动时的触摸事件 */    private class mSidebarTouchListenerRight implements OnTouchListener {        @Override        public boolean onTouch(View v, MotionEvent event) {            int touchX = (int) event.getX();            switch (event.getAction()) {            // 按下            case MotionEvent.ACTION_DOWN:                mElements.mMotionX = touchX;                return true;                // 移动            case MotionEvent.ACTION_MOVE:                mElements.mdeteX = touchX - mElements.mMotionX;                if (mElements.mSideLayout.getScrollX() - mElements.mdeteX                        + mElements.menuspace >= 0                        && mElements.mSideLayout.getScrollX()                                - mElements.mdeteX <= 0) {                    mElements.mSideLayout.scrollBy(-mElements.mdeteX, 0);                }                return true;                // 抬起            case MotionEvent.ACTION_UP:                if(Math.abs(mElements.mdeteX)<24){                    if(mElements.mSideLayout.getScrollX()==mElements.menuspace){                        close();                    }                }                if (mElements.mSideLayout.getScrollX() > -mElements.menuspace >> 1) {                    if(!mScroller.computeScrollOffset()){                        mScroller.setFinalY(0);                        mScroller.setFinalX(0);                        mScroller.startScroll(mElements.mSideLayout.getScrollX(), 0,                                 mScroller.getFinalX()-mElements.mSideLayout.getScrollX(), 0, 300);                        mElements.mSideLayout.invalidate();                    }                } else {                    if(!mScroller.computeScrollOffset()){                        mScroller.setFinalY(0);                        mScroller.setFinalX(-mElements.menuspace);                        mScroller.startScroll(mElements.mSideLayout.getScrollX(), 0,                                 mScroller.getFinalX()-mElements.mSideLayout.getScrollX(), 0, 300);                        mElements.mSideLayout.invalidate();                    }                }                mElements.mdeteX = 0;                mElements.mMotionX = 0;                return true;            }            return false;        }    }    /** 左边触摸时实现的事件 */    private class mSidebarTouchListenerLeft implements OnTouchListener {        @Override        public boolean onTouch(View v, MotionEvent event) {            int touchX = (int) event.getX();            switch (event.getAction()) {            // 按下            case MotionEvent.ACTION_DOWN:                mElements.mMotionX = touchX;                return true;                // 滑动            case MotionEvent.ACTION_MOVE:                if (mElements.mMotionX == 0) {                    mElements.mMotionX = touchX;                }                mElements.mdeteX = touchX - mElements.mMotionX;                if (mElements.mSideLayout.getScrollX() - mElements.menuspace                        - mElements.mdeteX <= 0                        && mElements.mSideLayout.getScrollX()                                - mElements.mdeteX >= 0) {                    mElements.mSideLayout.scrollBy(-mElements.mdeteX, 0);                }                return true;                // 抬起            case MotionEvent.ACTION_UP:                if(Math.abs(mElements.mdeteX)<24){                    if(mElements.mSideLayout.getScrollX()==mElements.menuspace){                        close();                    }                }                if (mElements.mSideLayout.getScrollX() >= mElements.menuspace >> 1) {                    if (!mScroller.computeScrollOffset()) {                        mScroller.setFinalX(mElements.menuspace);                        mScroller.setFinalY(0);                        mScroller.startScroll(                                mElements.mSideLayout.getScrollX(),                                0,                                mScroller.getFinalX()                                        - mElements.mSideLayout.getScrollX(),                                0, 300);                        mElements.mSideLayout.invalidate();                    }                } else {                    if (!mScroller.computeScrollOffset()) {                        mScroller.setFinalX(0);                        mScroller.setFinalY(0);                        mScroller.startScroll(                                mElements.mSideLayout.getScrollX(), 0,                                -mElements.mSideLayout.getScrollX(), 0, 300);                        mElements.mSideLayout.invalidate();                    }                }                mElements.mdeteX = 0;                mElements.mMotionX = 0;                return true;            }            return false;        }    }

留下给主页面和菜单页面添加页面的接口:

/** 给主页添加页面 */    public void addViewtoMenu(View view) {        mElements.mMenuLayout.addView(view, new LayoutParams(                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));    }    /** 给菜单栏添加页面 */    public void addViewtoSide(View view, SideViewinterface sideViewinterface) {        SideView mSideView = new SideView(mElements.mContext, sideViewinterface);        mSideView.addView(view);        mElements.mSideLayout.addView(mSideView, new LayoutParams(                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));        if (mElements.mConfig.setSidebarPosition() % 2 == 0) {            mSideView.setOnTouchListener(new mSidebarTouchListenerLeft());        }        if (mElements.mConfig.setSidebarPosition() % 2 == 1) {            mSideView.setOnTouchListener(new mSidebarTouchListenerRight());        }    }

对侧边栏的一些事件的判断:

/**     * 判断其是否打开     * @return     */    public boolean isopen() {        if (mElements.mSideLayout.getScrollX() == 0) {            return false;        } else {            return true;        }    }    /**     * 打开侧边栏     */    public void open() {        if (!mScroller.computeScrollOffset()) {            if(mElements.mConfig.setSidebarPosition()%2==0){                mScroller.setFinalX(mElements.menuspace);                mScroller.setFinalY(0);                mScroller.startScroll(mElements.mSideLayout.getScrollX(), 0,                        mElements.menuspace, 0, 600);            }else{                mScroller.setFinalX(-mElements.menuspace);                mScroller.setFinalY(0);                mScroller.startScroll(mElements.mSideLayout.getScrollX(), 0,                        mScroller.getFinalX()-mElements.mSideLayout.getScrollX(), 0, 600);            }            mElements.mSideLayout.invalidate();        }    }    /**     * 关闭侧边栏     */    public void close() {        if (!mScroller.computeScrollOffset()) {            if(mElements.mConfig.setSidebarPosition()%2==0){                mScroller.setFinalX(0);                mScroller.setFinalY(0);                mScroller.startScroll(mElements.mSideLayout.getScrollX(), 0,                        mScroller.getFinalX()-mElements.mSideLayout.getScrollX(), 0, 600);            }else{                mScroller.setFinalX(0);                mScroller.setFinalY(0);                mScroller.startScroll(mElements.mSideLayout.getScrollX(), 0,                        mScroller.getFinalX()-mElements.mSideLayout.getScrollX(), 0, 600);            }            mElements.mSideLayout.invalidate();        }    }

最后,将出现的SideView代码贴出,主要是为了给应用留出事件接口:

/** * @author 徐晔 */public class SideView extends RelativeLayout{    /**     * @author 徐晔     * @note 传递触摸事件的接口     */    public static interface SideViewinterface {        /**传递onInterceptTouchEvent*/        public boolean onSideInterceptTouchEvent(MotionEvent ev);    }    private SideViewinterface mSideViewinterface;    public SideView(Context context, AttributeSet attrs, int defStyle,SideViewinterface mSideViewinterface ) {        super(context, attrs, defStyle);        this.mSideViewinterface=mSideViewinterface;    }    public SideView(Context context, AttributeSet attrs,SideViewinterface mSideViewinterface ) {        super(context, attrs);        this.mSideViewinterface=mSideViewinterface;    }    public SideView(Context context,SideViewinterface mSideViewinterface ) {        super(context);        this.mSideViewinterface=mSideViewinterface;    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        return mSideViewinterface.onSideInterceptTouchEvent(ev);    }    /** 测量位置 */    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        int childCount = getChildCount();        for (int i = 0; i < childCount; i++) {            View childView = getChildAt(i);            int measuredWidth = childView.getMeasuredWidth();            int measuredHeight = childView.getMeasuredHeight();            childView.layout(0, 0, measuredWidth, measuredHeight);        }    }    /** 度量大小 */    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        measureChildren(widthMeasureSpec, heightMeasureSpec);        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int heightSize = MeasureSpec.getSize(heightMeasureSpec);        setMeasuredDimension(widthSize, heightSize);    }}

现在代码贴完了;大家只要注意这几个类:
1:Scroller对象,实现平移动画的类
2:onMeasure,和onLayout方法,控制页面大小和进行排版布局的控制;
3:对事件传递控制
以上就是实现自定义View的根本,其实任何自定义View都可以如此实现。大家可以先从需求分析,然后在进行具体的代码书写。今天有侧边栏,但是如果是别的页面呢?其实原理是一样的。如何摆放,对手势的操作反馈,事件传递的控制,动画的实现;将问题分解为一块一块的,那样解决问题就游刃有余。

0 0