几篇文章的感谢
invalidate()的讲解http://www.eoeandroid.com/thread-196932-1-1.html
ViewGroup.layout(int l, int t, int r, int b)的讲解http://www.myexception.cn/mobile/1629261.html
startScroll(int startX, int startY, int dx, int dy, int duration)参数的意思:http://blog.csdn.net/tenpage/article/details/8220101
这是效果图,点击按钮实现蓝色区域的收起和展开,说一下思路:
ViewGroup因为理论上是无限宽和无限高的,他显示的只是屏幕 的那一块区域,所以当点击按钮时,可以通过ViewGroup的滚动,让蓝色区域移动到屏幕下方和移动上来,实现收起和展开,并通过invalidate()请求View树进行重绘,通过跟踪源码可以发现invalidate()调用了 invalidate(true),invalidate(true)函数的执行流程:
1、首先调用skipInvalidate(),该函数主要判断该View是否不需要重绘,如果不许要重绘则直接返回,不需要重绘的条件是该View不可见并且未进行动画
2、接下来的if语句是来进一步判断View是否需要绘制,其中表达式 (mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)的意思指的是如果View需要重绘并且其大小不为0,如果需要重绘,则处理相关标志位
3、对于开启硬件加速的应用程序,则调用父视图的invalidateChild函数绘制整个区域,否则只绘制dirty区域(r变量所指的区域),这是一个向上回溯的过程,每一层的父View都将自己的显示区域与传入的刷新Rect做交集。
invalidate()的讲解主要来自http://www.eoeandroid.com/thread-196932-1-1.html
思路基本上就是通过对ViewGroup的移动让其超出屏幕,在看一下具体的实现:
<span style="font-size:18px;">import android.app.Activity; import android.content.Context; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.Scroller; public class CustomDownBarView extends ViewGroup { private Context context; private int distance;//移动的距离 private int duration;//时间长短 private Scroller scroller; private View[] views; private int screenWidth, screenHeight; private boolean isOpenMenu; private static final String TAG="CustomDownBarView"; public CustomDownBarView(Context context) { super(context); this.initCustomDownBarView(context, null, 0); } public CustomDownBarView(Context context, AttributeSet attrs) { super(context, attrs); this.initCustomDownBarView(context, attrs, 0); } public CustomDownBarView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); this.initCustomDownBarView(context, attrs, defStyle); } /** * @param context * @param attrs * @param defStyle */ private void initCustomDownBarView(Context context, AttributeSet attrs, int defStyle) { this.context = context; this.isOpenMenu = false; this.views = new View[2]; this.scroller = new Scroller(context); this.initScreenWidthAndHeight(); this.duration = (duration == 0 ? 500 : duration); this.distance = (distance == 0 ? screenHeight * 1 / 3 : distance); } /** */ private void initScreenWidthAndHeight() { DisplayMetrics displayMetrics = new DisplayMetrics(); ((Activity) context).getWindow().getWindowManager().getDefaultDisplay() .getMetrics(displayMetrics); // ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); screenWidth = displayMetrics.widthPixels; screenHeight = displayMetrics.heightPixels; } /** */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); Log.i(TAG, "onMeasure"); int measuredWidth = MeasureSpec.getSize(widthMeasureSpec); int measuredHeight = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(measuredWidth, measuredHeight); for (int i = 0; i < getChildCount(); i++) { views[i] = getChildAt(i); ViewGroup.LayoutParams layoutParams = views[i].getLayoutParams(); int widthSpec = this.measureWidthAndHeight(layoutParams.width, measuredWidth); int heightSpec = this.measureWidthAndHeight(layoutParams.height, measuredHeight); views[i].measure(widthSpec, heightSpec); } } /** * @param widthOrHeight * @param measureSpec * @return */ private int measureWidthAndHeight(int widthOrHeight, int measureSpec) { //MeasureSpec作用:父布局对子布局的布局要求 //每个MeasureSpec代表了一组宽度和高度的要求。一个MeasureSpec由大小和模式组成。 //它有三种模式: //1.UNSPECIFIED(未指定),父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小; //2.EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小; //3.AT_MOST(至多),子元素至多达到指定大小的值 int result = MeasureSpec.makeMeasureSpec(widthOrHeight, MeasureSpec.EXACTLY); if (widthOrHeight > 0) { result = MeasureSpec.makeMeasureSpec(widthOrHeight, MeasureSpec.EXACTLY); } else if (widthOrHeight == -1) { result = MeasureSpec.makeMeasureSpec(measureSpec, MeasureSpec.UNSPECIFIED); } else if (widthOrHeight == -2) { result = MeasureSpec.makeMeasureSpec(measureSpec, MeasureSpec.AT_MOST); } return result; } /** */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { Log.i(TAG,"onLayout"); try { views[0].layout(0, 0, screenWidth, screenHeight); views[1].layout(0, screenHeight, screenWidth, screenHeight * 4 / 3); scrollTo(0, distance); isOpenMenu = true; } catch (Exception e) { e.printStackTrace(); } } /** */ @Override public void computeScroll() { if (scroller.computeScrollOffset()) { //getCurrX距离原点X方向的绝对值 scrollTo(scroller.getCurrX(), scroller.getCurrY()); invalidate(); } } /** */ public void showDownToolBarView() { isOpenMenu = true; //滚动,startX, startY为开始滚动的位置,dx,dy为滚动的偏移量, duration为完成滚动的时间 //startX 表示起点在水平方向到原点的距离(可以理解为X轴坐标,但与X轴相反),正值表示在原点左边,负值表示在原点右边。 //dx 表示滑动的距离,正值向左滑,负值向右滑。 Log.i(TAG, ""+distance); this.scroller.startScroll(0, getScrollY(), 0, distance, duration); invalidate(); } /** */ public void closeDownToolBarView() { isOpenMenu = false; Log.i(TAG, "close:"+getScrollY()); scroller.startScroll(0, getScrollY(), 0, -distance, duration); invalidate(); } public int getDistance() { return distance; } public void setDistance(int distance) { this.distance = distance; } public int getDuration() { return duration; } public void setDuration(int duration) { this.duration = duration; } public boolean isOpenMenu() { return isOpenMenu; } public void setOpenMenu(boolean isOpenMenu) { this.isOpenMenu = isOpenMenu; } }</span>继承ViewGroup,重写onMeasure和onLayout,测量过程在onMeasure中实现,是从树的顶端由上到下进行的, 在measure pass的最后,每一个View都存储好了自己的测量结果,布局过程(layout pass)仍然是从上到下进行(top-down),在这一遍中,每一个parent都会负责用测量过程中得到的尺寸,把自己的所有孩子放在正确的地方
<span style="font-size:18px;">views[0].layout(0, 0, screenWidth, screenHeight); views[1].layout(0, screenHeight, screenWidth, screenHeight * 4 / 3);</span>
<span style="font-size:18px;">就是将子view放在CustomDownBarView中,这里看过一片讲layout(int l, int t, int r, int b)的,讲的很清楚:<a target=_blank href="http://www.myexception.cn/mobile/1629261.html">http://www.myexception.cn/mobile/1629261.html</a></span>
<span style="font-size:18px;">接着是MainActivity</span>
<span style="font-size:18px;">import android.os.Bundle; import android.app.Activity; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.widget.ImageView; public class MainActivity extends Activity { private CustomDownBarView downBarView; private View mainView, toolView; private ImageView ivDownBar; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); downBarView = (CustomDownBarView) findViewById(R.id.custom_downbar_view); LayoutInflater layoutInflater = LayoutInflater.from(this); mainView = layoutInflater.inflate(R.layout.downbar_main_view, null); toolView = layoutInflater.inflate(R.layout.downbar_tool_view, null); downBarView.addView(mainView); downBarView.addView(toolView); ivDownBar = (ImageView) mainView .findViewById(R.id.downbar_main_imageview); ivDownBar.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (downBarView.isOpenMenu()) { downBarView.closeDownToolBarView(); } else { downBarView.showDownToolBarView(); } } }); } }</span>
<span style="font-size:18px;">downBarView.addView(mainView); downBarView.addView(toolView);</span>将你在xml中的布局找出并添加到CustomDownBarView中,然后看下是怎么展开和收起的
<span style="font-size:18px;">downBarView.showDownToolBarView();和downBarView.closeDownToolBarView();</span>这里最终要的就是scroller.startScroll(0, getScrollY(), 0, -distance, duration);,对CustomDownBarView的滚动,,这里有一个看过的对
<span style="font-size:18px;">startScroll http://blog.csdn.net/tenpage/article/details/8220101和对scrollerhttp://www.cnblogs.com/tt_mc/p/3585390.html的讲解</span>