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

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

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

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

他在Phone上的展现如下:

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

也就是选中一个扇形之后, 显示下一级菜单. 当然你也可以自定义成那种围绕内层圆弧再次展开的外层的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);

    }

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

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

首先看一下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的原理有了一些了解~

    原文作者:移动开发
    原文地址: https://my.oschina.net/sfshine/blog/213982
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞