Android自定义View之侧边栏初探

前面写了几篇关于进程/线程原理的;感觉和应用层关系不大;但是实际上,写代码和做事情一样,都注重的是一个严谨的思维习惯;如果你真的深刻的理解了底层进程/线程交互模型,内存管理等;那么对你做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都可以如此实现。大家可以先从需求分析,然后在进行具体的代码书写。今天有侧边栏,但是如果是别的页面呢?其实原理是一样的。如何摆放,对手势的操作反馈,事件传递的控制,动画的实现;将问题分解为一块一块的,那样解决问题就游刃有余。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值