前面說的這些東西其實都是重寫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);
//相似於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中拿到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); } } }
爲了方便學習, 把整個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的原理有了一些瞭解~