文档章节

Android Browser学习十 快捷菜单模块: PieMenu的实现(2)

SuShine
 SuShine
发布于 2014/03/28 22:19
字数 1549
阅读 685
收藏 36

前面说的这些东西其实都是重写view实现view的自由绘制, 但是有些时候, 可能我们还需要这样一种自定义的view, 他们其实

不能称得上是view, 而只是一些对视图的描述, 这很像我们使用animator的时候, 也会定义一些shape, 然后随着时间控制shape的变化, 然后把这些shape draw到画布上.呈现给用户.其实一定程度是实现了一个自己的"view系统".

同样, 在android浏览器的Piemenu中也有这样的类, 这就是PieView, 我感觉更应该叫 SubPieView.

他在Phone上的展现如下:

也就是选中一个扇形之后, 显示下一级菜单. 当然你也可以自定义成那种围绕内层圆弧再次展开的外层的menu.

我们看一下人是如何实现的.首先是其接口:


public interface PieView {

        public interface OnLayoutListener {
            public void onLayout(int ax, int ay, boolean left);
        }
        //同意设置布局监听, 在布局的时候做一些事情, 这里主要是刷新tab
        public void setLayoutListener(OnLayoutListener l);
        //父亲只管layout, 至于以何种方式展现, 孩子来决定
        public void layout(int anchorX, int anchorY, boolean onleft, float angle);
        //父亲只管draw, 至于以何种方式展现, 孩子来决定
        public void draw(Canvas c);
        //父亲只管发出onTouchEvent, 至于响应什么事件, 孩子来决定
        public boolean onTouchEvent(MotionEvent evt);

    }



设计图如下,在不同的设备(手机和平板)上显示的是不同的,.



首先看一下setAdapter也就是view的组装:

这个调用是在PieControlPhone::populateMenu()中实现调用的:


stack.setAdapter(mTabAdapter);



mTabAdapter还是一个BaseAdapter, 其实这个东西的实现可以认识是一个mini版的listview



//类似于listview的setAdapter
    public void setAdapter(Adapter adapter) {
        mAdapter = adapter;
        if (adapter == null) {
            if (mAdapter != null) {
                mAdapter.unregisterDataSetObserver(mObserver);
            }
            mViews = null;
            mCurrent = -1;
        } else {
        	//注册监听, 通知 从adapter中拿到各个view进行拼接
            mObserver = new DataSetObserver() {
                @Override
                public void onChanged() {
                    buildViews();
                }

                @Override
                public void onInvalidated() {
                    mViews.clear();
                }
            };
            //监听更新的通知, 对应adapter的 notifyDataSetChanged
            mAdapter.registerDataSetObserver(mObserver);
            setCurrent(0);
        }
    }



我们也可以看看我们熟悉的 adapter.getView的原理:



//从adapter中拿到view进行组装
    protected void buildViews() {
        if (mAdapter != null) {
            final int n = mAdapter.getCount();
            if (mViews == null) {
                mViews = new ArrayList<View>(n);
            } else {
                mViews.clear();
            }
            mChildWidth = 0;
            mChildHeight = 0;
            for (int i = 0; i < n; i++) {
                View view = mAdapter.getView(i, null, null);//展示了adapter也可以在其他情况中使用 (非listview), 可以用来把数据转换为view显示
                view.measure(View.MeasureSpec.UNSPECIFIED,
                        View.MeasureSpec.UNSPECIFIED);
                mChildWidth = Math.max(mChildWidth, view.getMeasuredWidth());
                mChildHeight = Math.max(mChildHeight, view.getMeasuredHeight());
                mViews.add(view);
            }
        }
    }



通过这两步就可以组装成一个view列表了,当然listview还有view的复用等, 这个view没有实现这些功能.



为了方便学习, 把整个BasePieView贴上, 代码也不多:


public abstract class BasePieView implements PieMenu.PieView {

    protected Adapter mAdapter;
    private DataSetObserver mObserver;
    protected ArrayList<View> mViews;

    protected OnLayoutListener mListener;

    protected int mCurrent;
    protected int mChildWidth;
    protected int mChildHeight;
    protected int mWidth;
    protected int mHeight;
    protected int mLeft;
    protected int mTop;

    public BasePieView() {
    }

    public void setLayoutListener(OnLayoutListener l) {
        mListener = l;
    }
    
   //类似于listview的setAdapter
    public void setAdapter(Adapter adapter) {
        mAdapter = adapter;
        if (adapter == null) {
            if (mAdapter != null) {
                mAdapter.unregisterDataSetObserver(mObserver);
            }
            mViews = null;
            mCurrent = -1;
        } else {
        	//注册监听, 通知 从adapter中拿到各个view进行拼接
            mObserver = new DataSetObserver() {
                @Override
                public void onChanged() {
                    buildViews();
                }

                @Override
                public void onInvalidated() {
                    mViews.clear();
                }
            };
            //监听更新的通知, 对应adapter的 notifyDataSetChanged
            mAdapter.registerDataSetObserver(mObserver);
            setCurrent(0);
        }
    }

    public void setCurrent(int ix) {
        mCurrent = ix;
    }

    public Adapter getAdapter() {
        return mAdapter;
    }
    //从adapter中拿到view进行组装
    protected void buildViews() {
        if (mAdapter != null) {
            final int n = mAdapter.getCount();
            if (mViews == null) {
                mViews = new ArrayList<View>(n);
            } else {
                mViews.clear();
            }
            mChildWidth = 0;
            mChildHeight = 0;
            for (int i = 0; i < n; i++) {
                View view = mAdapter.getView(i, null, null);//展示了adapter也可以在其他情况中使用 (非listview), 可以用来把数据转换为view显示
                view.measure(View.MeasureSpec.UNSPECIFIED,
                        View.MeasureSpec.UNSPECIFIED);
                mChildWidth = Math.max(mChildWidth, view.getMeasuredWidth());
                mChildHeight = Math.max(mChildHeight, view.getMeasuredHeight());
                mViews.add(view);
            }
        }
    }

    /**
     * this will be called before the first draw call
     * needs to set top, left, width, height
     * 被父亲控制, 开始布局 
     */
    @Override
    public void layout(int anchorX, int anchorY, boolean left, float angle) {
        if (mListener != null) {
            mListener.onLayout(anchorX, anchorY, left);
        }
    }

    //让孩子们去画着玩吧!
    @Override
    public abstract void draw(Canvas canvas);

    protected void drawView(View view, Canvas canvas) {
        final int state = canvas.save();
        canvas.translate(view.getLeft(), view.getTop());
        view.draw(canvas);
        canvas.restoreToCount(state);
    }
    //有孩子来决定 根据y判断当前选中view的策略
    protected abstract int findChildAt(int y);

    @Override
    public boolean onTouchEvent(MotionEvent evt) {
        int action = evt.getActionMasked();
        int evtx = (int) evt.getX();
        int evty = (int) evt.getY();
        if ((evtx < mLeft) || (evtx >= mLeft + mWidth)
                || (evty < mTop) || (evty >= mTop + mHeight)) {
            return false;
        }
        switch (action) {
            case MotionEvent.ACTION_MOVE:
                View v = mViews.get(mCurrent);
                setCurrent(Math.max(0, Math.min(mViews.size() -1,//设置显示那个tab
                        findChildAt(evty))));//更加y来判断 指向了哪个tab
                View v1 = mViews.get(mCurrent);
                if (v != v1) {
                    v.setPressed(false);
                    v1.setPressed(true);
                }
                break;
            case MotionEvent.ACTION_UP://在手机抬起的时候 通知相应点击事件
                mViews.get(mCurrent).performClick();
                mViews.get(mCurrent).setPressed(false);
                break;
            default:
                break;
        }
        return true;
    }

}




然后是一种孩子的实现, 也很简单:


/**
 * shows views in a stack
 * 这是一个显示层叠tab 视图的view适用于phone
 */
public class PieStackView extends BasePieView {

    private static final int SLOP = 5;

    private OnCurrentListener mCurrentListener;
    private int mMinHeight;

    public interface OnCurrentListener {
        public void onSetCurrent(int index);
    }

    public PieStackView(Context ctx) {
        mMinHeight = (int) ctx.getResources()
                .getDimension(R.dimen.qc_tab_title_height);
    }

    public void setOnCurrentListener(OnCurrentListener l) {
        mCurrentListener = l;
    }
   //当前选中哪个?
    @Override
    public void setCurrent(int ix) {
        super.setCurrent(ix);
        if (mCurrentListener != null) {
            mCurrentListener.onSetCurrent(ix);
            buildViews();//从adapter中拿到view
            layoutChildrenLinear();
        }
    }

    /**
     * this will be called before the first draw call
     * 在绘制之前会被调用进行layout
     */
    @Override
    public void layout(int anchorX, int anchorY, boolean left, float angle) {
        super.layout(anchorX, anchorY, left, angle);
        buildViews();
        mWidth = mChildWidth;
        mHeight = mChildHeight + (mViews.size() - 1) * mMinHeight;
        mLeft = anchorX + (left ? SLOP : -(SLOP + mChildWidth));
        mTop = anchorY - mHeight / 2;
        if (mViews != null) {
            layoutChildrenLinear();
        }
    }
    //布局其中的child
    private void layoutChildrenLinear() {
        final int n = mViews.size();
        int top = mTop;
        int dy = (n == 1) ? 0 : (mHeight - mChildHeight) / (n - 1);
        for (View view : mViews) {
            int x = mLeft;
            view.layout(x, top, x + mChildWidth, top + mChildHeight);//重叠的tab缩略图进行布局
            top += dy;
        }
    }
   //真正的绘制view, 谁想显示这个view就把他的canvas传给他就可以绘制出来了.
    @Override
    public void draw(Canvas canvas) {
        if (mViews != null) {
            final int n = mViews.size();
            for (int i = 0; i < mCurrent; i++) {
                drawView(mViews.get(i), canvas);
            }
            for (int i = n - 1; i > mCurrent; i--) {
                drawView(mViews.get(i), canvas);
            }
            //先绘制其他的tab 最后绘制当前的tab这样就可以把当前的tab整个显示出来,其他tab被他遮挡一部分了
            drawView(mViews.get(mCurrent), canvas);
        }
    }
    //被父亲调用, 用来更加用户的y查看选中的是哪个tab
    @Override
    protected int findChildAt(int y) {
        final int ix = (y - mTop) * mViews.size() / mHeight;
        return ix;
    }

}



这样其实我们学习到了另一个自定义view的方式, 完全不继承view, 谁想显示这个view把canvas给我们的view就可以显示相应的展现

也对listview和adapter的原理有了一些了解~




© 著作权归作者所有

共有 人打赏支持
SuShine
粉丝 126
博文 574
码字总数 157991
作品 0
朝阳
后端工程师
私信 提问
Android Browser学习九 快捷菜单模块: PieControl的架构

今天介绍一下浏览器PieMenu的实现, Piemenu是浏览器的一个实验室功能, 但是其效果还是挺炫的如下所示: 估计很多app都的扇形菜单都参考过这个东西.而且这个东西一直起来也不难, 解耦做的还是比...

SuShine
2014/03/26
0
0
基于 OSGi 的 Swing 客户端开发实践

简介: 随着 OSGi 技术迅猛发展,插件化开发技术得到了更为广泛的关注,同时也涌现出了 Equinox、Felix 等众多基于 OSGi 规范的开源框架。但目前相关技术文章主要关注的是 OSGi 同 JavaEE 技...

IBMdW
2011/11/18
1K
4
Qt 5.5 Alpha 发布,完全支持 Canvas 3D

Qt 5.5 alpha 发布,Qt 5.5 完全支持 Canvas 3D;等待已久的 Qt 3D 技术预览。 Qt 5.5 还引入了 Qt Location 技术预览;Qt Multimedia 支持 GStreamer 1.0;新增 TreeView 控件;提供 Enterp...

oschina
2015/03/18
10.4K
30
Android Browser学习二 BrowserActivity 的初始化 --其他重要模块

BrowserActivity 是浏览器的核心Activity了, 是浏览器的入口, 但是他里面并没有出来很多复杂的逻辑, 只是实现一些android 系统对activity的回调. 这些逻辑交给了Controller来处理, 就让我们一...

SuShine
2014/02/08
0
0
Zepto.js 1.0 正式发布,移动 JavaScript 框架

Zepto 是一个轻量级的 JS 库,从当初 2~5k 大小到正式发布,仍保持不到 10k 的体积,能减少下载和与运行时间;兼容大多数移动浏览器和主流桌面现代浏览器,支持 Detect 和 Touch 模块实现浏览...

oschina
2013/03/04
4.7K
9

没有更多内容

加载失败,请刷新页面

加载更多

CSS,JavaScript实现手风琴导航菜单

<!DOCTYPE html><html><head> <title>Side Navigator Demo</title> <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"> </script> <link re......

yuewawa
31分钟前
1
0
mysql 系统设置SQL

打开、关闭日志 SET GLOBAL general_log = 'Off'; SET GLOBAL general_log = 'On'; 查看日志是否打开 show variables like '%general%';...

jingshishengxu
今天
3
0
转行学大数据,如何选择如何学习大数据开发?

大数据火了几年了,但是今年好像进入了全民大数据时代,本着对科学的钻(zhun)研(bei)精(tiao)神(cao),我在17年年初开始自学大数据,后经过系统全面学习,于这个月跳槽到现任公司。 现在已经...

董黎明
今天
5
0
RadosClient OSDC

RadosClient.h class librados::RadosClient : public Dispatcher//继承自Dispatcher(消息分发类){public: using Dispatcher::cct; md_config_t *conf;//配置文件private: ......

banwh
今天
3
0
如果让你写一个消息队列,该如何进行架构设计?

面试题 如果让你写一个消息队列,该如何进行架构设计?说一下你的思路。 面试官心理分析 其实聊到这个问题,一般面试官要考察两块: 你有没有对某一个消息队列做过较为深入的原理的了解,或者...

李红欧巴
今天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部