从启动界面到主界面之后的效果如图所示,采用的是v4包下的DrawerLayout, activity_main.xml文件如下:

<!-- A DrawerLayout is intended to be used as the top-level content view using match_parent for both width and height to consume the full space available. -->
<android.support.v4.widget.DrawerLayout
    android:id="@+id/drawer_layout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="net.oschina.app.ui.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <FrameLayout
            android:id="@+id/realtabcontent"
            android:layout_width="match_parent"
            android:layout_height="0dip"
            android:layout_weight="1"/>

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="?attr/windows_bg">

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="4dip">

                <net.oschina.app.widget.MyFragmentTabHost
                    android:id="@android:id/tabhost"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="4dip"/>

                <View
                    android:layout_width="match_parent"
                    android:layout_height="1px"
                    android:background="?attr/lineColor"/>

            </RelativeLayout>

            <!-- 快速操作按钮 -->

            <ImageView
                android:id="@+id/quick_option_iv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:contentDescription="@null"
                android:src="@drawable/btn_quickoption_selector"/>
        </FrameLayout>
    </LinearLayout>

    <!-- 左侧侧滑菜单 -->

    <fragment
        android:id="@+id/navigation_drawer"
        android:name="net.oschina.app.ui.NavigationDrawerFragment"
        android:layout_width="@dimen/navigation_drawer_width"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        tools:layout="@layout/fragment_navigation_drawer"/>

</android.support.v4.widget.DrawerLayout>

侧边栏布局fragment_navigation_drawer.xml的定义:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="?attr/layout_bg_normal" >
    <net.oschina.app.widget.CustomerScrollView
        android:layout_width="match_parent"
        android:layout_height="0dip"
        android:layout_weight="1">
        <include layout="@layout/fragment_navigation_drawer_items"/>
    </net.oschina.app.widget.CustomerScrollView >
    <include layout="@layout/fragment_navigation_drawer_foot"/>
</LinearLayout>

fragment_navigation_drawer_items.xml和fragment_navigation_drawer_foot.xml的布局比较简单,不再赘述。

主要介绍自定义控件CustomerScollView,继承了ScrollView:

package net.oschina.app.widget;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.TranslateAnimation;
import android.widget.ScrollView;

/**
 * 可以拖动的ScrollView
 *
 */
public class CustomerScrollView extends ScrollView {

	private static final int size = 4;
	private View inner;
	private float y;
	private Rect normal = new Rect();

	public CustomerScrollView(Context context) {
		super(context);
	}

	public CustomerScrollView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	@Override
	protected void onFinishInflate() {
		if (getChildCount() > 0) {
			inner = getChildAt(0);
		}
	}

	@SuppressLint("ClickableViewAccessibility")
	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		if (inner == null) {
			return super.onTouchEvent(ev);
		} else {
			commOnTouchEvent(ev);
		}
		return super.onTouchEvent(ev);
	}

	public void commOnTouchEvent(MotionEvent ev) {
		int action = ev.getAction();
		switch (action) {
		case MotionEvent.ACTION_DOWN:
			y = ev.getY();
			break;
		case MotionEvent.ACTION_UP:
			if (isNeedAnimation()) {
				// Log.v("mlguitar", "will up and animation");
				animation();
			}
			break;
		case MotionEvent.ACTION_MOVE:
			final float preY = y;
			float nowY = ev.getY();
			/**
			 * size=4 表示 拖动的距离为屏幕的高度的1/4
			 */
			int deltaY = (int) (preY - nowY) / size;
			// 滚动
			// scrollBy(0, deltaY);

			y = nowY;
			if (isNeedMove()) {
				if (normal.isEmpty()) {
					normal.set(inner.getLeft(), inner.getTop(),
							inner.getRight(), inner.getBottom());
					return;
				}
				int yy = inner.getTop() - deltaY;

				// 移动布局
				inner.layout(inner.getLeft(), yy, inner.getRight(),
						inner.getBottom() - deltaY);
			}
			break;
		default:
			break;
		}
	}

	public void animation() {
		TranslateAnimation ta = new TranslateAnimation(0, 0, inner.getTop(),
				normal.top);
		ta.setDuration(200);
		inner.startAnimation(ta);
		inner.layout(normal.left, normal.top, normal.right, normal.bottom);
		normal.setEmpty();
	}

	public boolean isNeedAnimation() {
		return !normal.isEmpty();
	}

	public boolean isNeedMove() {
		int offset = inner.getMeasuredHeight() - getHeight();
		int scrollY = getScrollY();
		if (scrollY == 0 || scrollY == offset) {
			return true;
		}
		return false;
	}

}

再来看看效果:

可以看到向上滑动的时候,会有明显回弹的效果。

oschina客户端滑动菜单的View的布局使用了可以拖拽的ScrollView,类文件为CustomerScrollView。

  • 拖拽的目标是ScrollView内的菜单的布局View,所以在CustomerScrollView内的onFinishInflate()函数中首先通过getChildAt(0)来获取菜单布局的View,这就是第一步的目标是获取要拖拽的对象。onFinishInflate()加载完view,这里的View指的就是源码中<include layout="@layout/fragment_navigation_drawer_items"/>加载的布局文件。
  • 拖拽的过程实际上是一个“按下-移动-抬起”的过程,因此要重写onTouchEvent(MotionEvent ev),其中移动过程实际上是将菜单view按照移动的方向和距离,怎么实现这个功能呢?源码中最关键的就是这行代码inner.layout(inner.getLeft(), yy, inner.getRight(),inner.getBottom() - deltaY);这个方法四个参数都是inner相对其父控件ScrollView的坐标原点而言的。不是很了解的,可以专门查查坐标的相关知识。
  • 当手指抬起也就是MotionEvent.ACTION_UP事件发生时,将拖拽后的view恢复移动到原来位置,移动过程附加了一个动画,由于移动实际上是位置发生了变化,因此用到了TranslateAnimation,因为是上下拖拽,所以X的起始和终止坐标都是0,Y的起始和终止坐标至于为什么那么写,相信看完博客应该就会明白了。那么问题来了,要自动移动回去,那么触发的时机在MotionEvent.ACTION_UP中,原来的位置怎么保存,因为移动时需要左上右下四个参数,因此在CustomerScrollView中我们看到了这样一个变量private Rect normal = new Rect();通过normal.set(inner.getLeft(), inner.getTop(),inner.getRight(), inner.getBottom());方法记录菜单view的初始化位置。
  • 经过仔细揣摩发现scrollY == 0这个条件实际上是滚动到了最顶部的时候,而scrollY == offset是滚动到最底部的时候,两个条件满足其中一个都可以实现拖拽的效果。int offset = inner.getMeasuredHeight() - getHeight();相当于本身的身高减去实际能看到的身高就等于没有看到的身高部分。
//是否需要移动
public boolean isNeedMove() {
   int offset = inner.getMeasuredHeight() - getHeight();
   int scrollY = getScrollY();
    if (scrollY == 0 || scrollY == offset) {
         return true;
          }
   return false;
}
  • 为什么源码中需要isNeedAnimation()这个函数呢?因为恢复到原来位置也用到了inner.layout(normal.left, normal.top, normal.right, normal.bottom);,因此normal首先必须要有四个参数值。而这个normal只有满足上面的条件后才有值的。
  • 为什么在拖拽发生又恢复到原来位置后,要把这个normal.setEmpty();置空呢?它的意图是什么?仔细想来,发现这个normal的set左上右下四个值时,是在满足2.4两种条件之一就会有具体值的。因此这个normal就会有两种不同的Rect.顶部的时候左上右下四个值分别为(0,0,实际菜单的宽度240dp,菜单的实际测量高度)而滚动到最底部的时候左上右下四个值分别为(0,负的【菜单的实际高度减去屏幕的高度】,实际菜单的宽度240dp,屏幕的高度),因此需要清空。

[Android]开源中国源码分析之二---DrawerLayout的更多相关文章

  1. [Android]开源中国源码分析之一---启动界面

    开源中国android端版本号:2.4 启动界面: 在AndroidManifest.xml中找到程序的入口, <activity android:name=".AppStart&qu ...

  2. Android开源框架源码分析:Okhttp

    一 请求与响应流程 1.1 请求的封装 1.2 请求的发送 1.3 请求的调度 二 拦截器 2.1 RetryAndFollowUpInterceptor 2.2 BridgeInterceptor ...

  3. Android 开源项目源码解析(第二期)

    Android 开源项目源码解析(第二期) 阅读目录 android-Ultra-Pull-To-Refresh 源码解析 DynamicLoadApk 源码解析 NineOldAnimations ...

  4. 一个普通的 Zepto 源码分析(二) - ajax 模块

    一个普通的 Zepto 源码分析(二) - ajax 模块 普通的路人,普通地瞧.分析时使用的是目前最新 1.2.0 版本. Zepto 可以由许多模块组成,默认包含的模块有 zepto 核心模块,以 ...

  5. Zepto源码分析(二)奇淫技巧总结

    Zepto源码分析(一)核心代码分析 Zepto源码分析(二)奇淫技巧总结 目录 * 前言 * 短路操作符 * 参数重载(参数个数重载) * 参数重载(参数类型重载) * CSS操作 * 获取属性值的 ...

  6. 插件开发之360 DroidPlugin源码分析(二)Hook机制

    转载请注明出处:http://blog.csdn.net/hejjunlin/article/details/52124397 前言:新插件的开发,可以说是为插件开发者带来了福音,虽然还很多坑要填补, ...

  7. DataTable源码分析(二)

    DataTable源码分析(二) ===================== DataTable函数分析 ---------------- DataTable作为整个插件的入口,完成了整个表格的数据初 ...

  8. Koa源码分析(二) -- co的实现

    Abstract 本系列是关于Koa框架的文章,目前关注版本是Koa v1.主要分为以下几个方面: Koa源码分析(一) -- generator Koa源码分析(二) -- co的实现 Koa源码分 ...

  9. Unity时钟定时器插件——Vision Timer源码分析之二

      Unity时钟定时器插件——Vision Timer源码分析之二 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com 前面的已经介绍了vp_T ...

随机推荐

  1. react import改为绝对路径

    最近在使用react时发现路径用../../很不方便,特别是修改项目结构时,加减../都能改到吐血, 所有在网上找了半天webpack的配置,特此记录下 module.exports = (webpa ...

  2. final和finally面试时最好的回答

    finally: try块必须和catch块或和finally同在,不能单独存在,二者必须出现一个. finally块总会执行,不论是否有错误出现.但是若try语句块或会执行的catch语句块使用了J ...

  3. 跟上Java8 - 日期和时间实用技巧,转自知乎王爵nice

    作者:王爵nice链接:https://zhuanlan.zhihu.com/p/28133858来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 当你开始使用Java操 ...

  4. SET ANSI_NULLS ON 在T-SQL中是什么意思

    from:https://www.cnblogs.com/kekong/p/6731321.html Transact-SQL 支持在与空值进行比较时,允许比较运算符返回 TRUE 或 FALSE. ...

  5. boost::lockfree::spsc_queue

    #include <boost/thread/thread.hpp> #include <boost/lockfree/spsc_queue.hpp> #include < ...

  6. 关于微信小程序下拉出现三个小点

    包子这天看美团外卖的小程序,再瞅瞅自己的背景色,发现,美团下拉的时候有三个小点,但是我自己的校车徐下拉的时候没有三个小点,很是郁闷,于是各种的找各种的找,发现,这三个小点是微信小程序自带的,你只需要设 ...

  7. tomcat 配置自签名ssl证书

    背景:据说17年苹果app必须走https协议与后台交互了,网上各种搜索最后还是发现有2篇写的比较全面,折腾后总结出2种实现方式.在这里记录,方便有共同需求的同学们参考 本文只介绍生成自签名ssl证书 ...

  8. h5-localStorage实现缓存ajax请求数据

    使用背景:要实现每次鼠标hover“能力雷达”,则显示能力雷达图(通过ajax请求数据实现雷达图数据显示),所以每次hover都去请求ajax会影响性能,因此这里要用到本地缓存. 实现: 此处是通过传 ...

  9. apply & call

    一.意义 function.apply(obj,args) obj代替function里this对象 args作为参数传给function 二.实例 1. obj代替function里this对象 f ...

  10. POJ 1470 Closest Common Ancestors【近期公共祖先LCA】

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/u013912596/article/details/35311489 题目链接:http://poj ...