[Android]開源中國源碼分析之二---DrawerLayout

来源:http://www.cnblogs.com/xiaomoxian/archive/2016/02/27/5222185.html
-Advertisement-
Play Games

從啟動界面到主界面之後的效果如圖所示,採用的是v4包下的DrawerLayout, activity_main.xml文件如下: ...


從啟動界面到主界面之後的效果如圖所示,採用的是v4包下的DrawerLayout, activity_main.xml文件如下:

777

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

再來看看效果:

jdfw

可以看到向上滑動的時候,會有明顯回彈的效果。

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,屏幕的高度),因此需要清空。

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 問題:IE8/9不支持Array.indexOf 解決方案 if (!Array.prototype.indexOf) { Array.prototype.indexOf = function(elt /*, from*/) { var len = this.length >>> 0; var fr
  • 1、CSS 簡介 CSS 指層疊樣式表 (Cascading Style Sheets),是一種用來表現 HTML 文檔樣式的語言,樣式定義如何顯示 HTML 元素,是能夠真正做到網頁表現與結構分離的一種樣式設計語言。樣式通常存儲在樣式表中,外部樣式表通常存儲在 CSS 文件中,多個樣式定義可層疊為
  • 這是因為 app.use(function * (){ 語句中有一個 * ,這種方式被稱為generator functions ,一般寫作function *(){...} 的形式,在此類function 中可以支持ES6的一種yield概念。 為了保證這種新型的方法可以編譯通過,在運行node 
  • This application's application-identifier entitlement does not match that of the installed application. These values must match for an upgrade to be a
  • 第一步、首先在你項目中創建一個包存放支持下拉刷新和上拉載入的類: 第二步、需要把兩個動畫導入進來,實現180度旋轉與360度旋轉: 第三步、需要把支持的下拉與上拉顯示的隱藏載入佈局給導入進來 第四步、需要添加strings.xml與colors.xml文件的內容添加到項目裡面: strings.xm
  • 分類:C#、Android、VS2015; 創建日期:2016-02-27 一、簡介 這一節演示如何利用以非同步方式(async、await)訪問SQLite資料庫。 二、示例4運行截圖 下麵左圖為初始頁面,右圖為單擊【創建資料庫】按鈕後的結果。 下麵左圖為單擊【添加單行】按鈕的結果,右圖為單擊【添加...
  • 近期因為項目的需要,研究了一下Android文件下載進度顯示的功能實現,接下來就和大家一起分享學習一下,希望對廣大初學者有幫助。 先上效果圖: 上方的藍色進度條,會根據文件下載量的百分比進行載入,中部的文本控制項用來現在文件下載的百分比,最下方的ImageView用來展示下載好的文件,項目的目的就是動
  • 作者: "@gzdaijie" 本文為作者原創,轉載請註明出處:http://www.cnblogs.com/gzdaijie/p/5222191.html 1.寫在前面 Android提供了豐富的 函數,本文介紹最常用的8種對話框的使用方法,包括普通(包含提示消息和按鈕)、列表、單選、多選、等待、
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...