前面说的这些东西其实都是重写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的原理有了一些了解~