现在的位置: 首页 > 综合 > 正文

继承ViewGroup实现布局的展开和隐藏

2014年10月04日 ⁄ 综合 ⁄ 共 7205字 ⁄ 字号 评论关闭

几篇文章的感谢

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

Scroller类的讲解http://www.cnblogs.com/tt_mc/p/3585390.html


这是效果图,点击按钮实现蓝色区域的收起和展开,说一下思路:

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的移动让其超出屏幕,在看一下具体的实现:

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

继承ViewGroup,重写onMeasure和onLayout,测量过程在onMeasure中实现,是从树的顶端由上到下进行的, 在measure pass的最后,每一个View都存储好了自己的测量结果,布局过程(layout pass)仍然是从上到下进行(top-down),在这一遍中,每一个parent都会负责用测量过程中得到的尺寸,把自己的所有孩子放在正确的地方

views[0].layout(0, 0, screenWidth, screenHeight);
views[1].layout(0, screenHeight, screenWidth, screenHeight * 4 / 3);
就是将子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>
接着是MainActivity
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();
				}
			}
		});
	}
}
downBarView.addView(mainView);
downBarView.addView(toolView);

将你在xml中的布局找出并添加到CustomDownBarView中,然后看下是怎么展开和收起的

downBarView.showDownToolBarView();和downBarView.closeDownToolBarView();


这里最终要的就是scroller.startScroll(0, getScrollY(), 0, -distance, duration);,对CustomDownBarView的滚动,,这里有一个看过的对

startScroll <a target=_blank href="http://blog.csdn.net/tenpage/article/details/8220101">http://blog.csdn.net/tenpage/article/details/8220101</a>和对scroller <a target=_blank href="http://www.cnblogs.com/tt_mc/p/3585390.html">http://www.cnblogs.com/tt_mc/p/3585390.html</a>的讲解

抱歉!评论已关闭.