ViewGroup做导航栏-----------开源中国客户端导航栏剖析

转帖请注明本文出自pkxutao的博客(http://blog.csdn.net/pkxutao/article/details/21093549),请尊重他人的辛勤劳动成果,谢谢!

众所周知,官方已经不推荐用TabActivity了,推荐用fragment,具体原因不去纠结。前段时间下载了开源中国的源码看了下,发现导航栏做法挺有意思的(或者很多人都这样做了只是我没发现),于是抽离了出来研究了下,下面来剖析具体做法。

先看布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#ffffff">
    
	<include layout="@layout/main_header" />   
    
    <com.example.testandroid.ScrollLayout   
	  android:id="@+id/main_scrolllayout"    
	  android:layout_width="fill_parent"    
	  android:layout_height="fill_parent"
   	  android:layout_weight="1">    
  
 	  <include layout="@layout/frame_news" />
      
	  <include layout="@layout/frame_question" />
	  
	  <include layout="@layout/frame_tweet" />
	      
	  <include layout="@layout/frame_active" />
       
	</com.example.testandroid.ScrollLayout> 
    
    <include layout="@layout/main_footer" />   

</LinearLayout>


很简单,结构也很清晰就是一个LinearLayout包含三个布局,上面一个头layout,中间一个自定义的layout,下面一个footlayout。头layout不说了,里面内容可以任意写,甚至可以把这个layout去掉,footlayout代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="horizontal" 
	android:id="@+id/main_linearlayout_footer"
	android:layout_width="fill_parent" 
	android:layout_height="wrap_content"
	android:gravity="center_vertical"
	android:background="@drawable/widget_bar_bg_n">
	<RadioButton 
		android:id="@+id/main_footbar_news"
		style="@style/main_footbar_radio"
		android:drawableTop="@drawable/widget_bar_news"/>
	<ImageView
		style="@style/main_footbar_cutline"
		android:src="@drawable/widget_bar_cut_off"/>
	<RadioButton 
		android:id="@+id/main_footbar_question"
		style="@style/main_footbar_radio"
		android:drawableTop="@drawable/widget_bar_question"/>
	<ImageView
		style="@style/main_footbar_cutline"
		android:src="@drawable/widget_bar_cut_off"/>
	<RadioButton 
		android:id="@+id/main_footbar_tweet"
		style="@style/main_footbar_radio"
		android:drawableTop="@drawable/widget_bar_tweet"/>
	<ImageView
		style="@style/main_footbar_cutline"
		android:src="@drawable/widget_bar_cut_off"/>
	<RadioButton
		android:id="@+id/main_footbar_active"
		style="@style/main_footbar_radio"
		android:drawableTop="@drawable/widget_bar_active"/>
	<ImageView
		style="@style/main_footbar_cutline"
		android:src="@drawable/widget_bar_cut_off"/>
	<ImageView
		android:id="@+id/main_footbar_setting"
		style="@style/main_footbar_image"
		android:src="@drawable/widget_bar_more"/>
</LinearLayout>

很简单,就是radiobutton,底部导航栏的布局。

重点在自定义布局ScrollLayout 。

先看onMeasure中是怎样计算每个view大小的:

@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		//Log.e(TAG, "onMeasure");
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		final int width = MeasureSpec.getSize(widthMeasureSpec);
		final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
		if (widthMode != MeasureSpec.EXACTLY) {
			throw new IllegalStateException(
					"ScrollLayout only canmCurScreen run at EXACTLY mode!");
		}
		final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
		if (heightMode != MeasureSpec.EXACTLY) {
			throw new IllegalStateException(
					"ScrollLayout only can run at EXACTLY mode!");
		}

		// The children are given the same width and height as the scrollLayout
		final int count = getChildCount();
		for (int i = 0; i < count; i++) {
			getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
		}
		// Log.e(TAG, "moving to screen "+mCurScreen);
		scrollTo(mCurScreen * width, 0);
	}

通过MeasureSpec.getSize和MeasureSpec.getMode分别得到了view的宽度和宽度模式。

onMeasure传入的widthMeasureSpecheightMeasureSpec不是一般的尺寸数值,而是将模式和尺寸组合在一起的数值。我们需要通过int mode = MeasureSpec.getMode(widthMeasureSpec)得到模式,用int size= MeasureSpec.getSize(widthMeasureSpec)。

这个mode有三种:


UNSPECIFIED:父VIEW对子VIEW无任何约束,子VIEW可以为任意大小

EXACTLY :固定大小

AT_MOST:子VIEW可以达到最大size。

这里如果宽高的模式不是固定大小则抛出异常,scrollTo函数在这里被调用是可以打开应用默认为第N个view。

接着来看onLayout是怎样布局的:

protected void onLayout(boolean changed, int l, int t, int r, int b) {
		int childLeft = 0;
		final int childCount = getChildCount();//得到子view的数目
		for (int i = 0; i < childCount; i++) {
			final View childView = getChildAt(i);
			if (childView.getVisibility() != View.GONE) {
				final int childWidth = childView.getMeasuredWidth();//得到子view宽度,在这里得到的宽度是屏幕的宽度
				Log.e(TAG, "childWidth:"+ childWidth+",childHeight:"+childView.getMeasuredHeight());
				childView.layout(childLeft, 0, childLeft + childWidth,
						childView.getMeasuredHeight());//布局子view位置,
				childLeft += childWidth;
			}
		}
	}
初始效果(默认显示第一个view):



UI完成后再来看滑动逻辑(推荐先看我之前的关于手势分发的代码)。

onInterceptTouchEvent代码:

public boolean onInterceptTouchEvent(MotionEvent ev) {
		//Log.e(TAG, "onInterceptTouchEvent-slop:" + mTouchSlop);
		final int action = ev.getAction();
		if ((action == MotionEvent.ACTION_MOVE)
				&& (mTouchState != TOUCH_STATE_REST)) {//如果是move事件且view在运动状态,则直接返回true,手势由onTouchEvent处理
			return true;
		}
		final float x = ev.getX();
		final float y = ev.getY();
		switch (action) {
		case MotionEvent.ACTION_MOVE:
			final int xDiff = (int) Math.abs(mLastMotionX - x);//滑动距离
			if (xDiff > mTouchSlop) {
				mTouchState = TOUCH_STATE_SCROLLING;//如果大于一个阈值,则把view状态设置为运动状态,然后把手势交给onTouchEvent处理
			}
			break;
		case MotionEvent.ACTION_DOWN:
			mLastMotionX = x;
			mLastMotionY = y;
			mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST
					: TOUCH_STATE_SCROLLING;
			break;
		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP:
			mTouchState = TOUCH_STATE_REST;
			break;
		}
		return mTouchState != TOUCH_STATE_REST;
	}

mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();


意思就是这个距离是我们认为用户想滑动屏幕之前的像素距离,通俗点来说就是超过这个值就可以判断用户是想滑动屏幕,而不是点击或者误触。

onTouchEvent代码就不贴了,里面有涉及到滑动速度问题,以后再分享。具体就是调用scrollBy移动view,使view能跟随手指滑动。

scrollBy和scrollTo的区别:


滑动到某一位置,为相对位置,如果参数为(10,10),则x和y方向分别滑动10单位距离,而不是滑动到(10,10)这个坐标。


滑动到某一位置,为绝对位置,如果参数为(10,10),则滑动到(10,10)这个坐标。


•scrollTo就是把View移动到屏幕的X和Y位置,也就是绝对位置。而scrollBy其实就是调用的scrollTo,但是参数是当前mScrollX和mScrollY加上X和Y的位置,所以ScrollBy调用的是相对于mScrollX和mScrollY的位置。我们在上面的代码中可以看到当我们手指不放移动屏幕时,就会调用scrollBy来移动一段相对的距离。而当我们手指松开后,会调用mScroller.startScroll(mUnboundedScrollX, 0, delta, 0,duration);来产生一段动画来移动到相应的页面,在这个过程中系统回不断调用computeScroll(),我们再使用scrollTo来把View移动到当前Scroller所在的绝对位置。



这样,子view就能根据跟随我们的手指滑动了,如果点击导航上的某个按钮,可以调用startScroll函数进行滑动动画,并且在滑动结束后调用OnViewChange接口来告诉Activity:我执行了滑动,你可以执行更新内容等动作了。

到此,导航栏布局已经完成,支持跟随手指滑动、滑动动画等,这种做法很简单也很实用。

demo下载



开源中国客户端源码

http://git.oschina.net/oschina/android-app


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值