Android Browser學習十 快捷菜單模塊: PieMenu的實現(2)

前面說的這些東西其實都是重寫view實現view的自由繪製, 可是有些時候, 可能咱們還須要這樣一種自定義的view, 他們其實 java

不能稱得上是view, 而只是一些對視圖的描述, 這很像咱們使用animator的時候, 也會定義一些shape, 而後隨着時間控制shape的變化, 而後把這些shape draw到畫布上.呈現給用戶.其實必定程度是實現了一個本身的"view系統". android

一樣, 在android瀏覽器的Piemenu中也有這樣的類, 這就是PieView, 我感受更應該叫 SubPieView. canvas

他在Phone上的展示以下: 瀏覽器

也就是選中一個扇形以後, 顯示下一級菜單. 固然你也能夠自定義成那種圍繞內層圓弧再次展開的外層的menu. ide

咱們看一下人是如何實現的.首先是其接口: 佈局


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()中實現調用的: ui


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貼上, 代碼也很少: this


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;
    }

}




而後是一種孩子的實現, 也很簡單: spa


/**
 * 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的原理有了一些瞭解~

相關文章
相關標籤/搜索