Launcher源碼淺析-----Launcher自定義布局屬性


   在上一篇文章Launcher源碼淺析-----Launcher布局》中,對Launcher的總布局文件Launcher.xml進行了分析。在分析Launcher布局文件代碼過程中,會看到一些以launcher:開頭而不是以android:開頭的布局屬性的定義。如launcher.xml文件中id為workspace的視圖布局,如下代碼:

<com.android.launcher2.DragLayer
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"

android:id="@+id/drag_layer"
android:layout_width="match_parent"
android:layout_height="match_parent">
...
<!-- Workspace即手機桌面,默認系統是包含可翻轉5頁 -->
<com.android.launcher2.Workspace
android:id="@+id/workspace"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="@dimen/qsb_bar_height_inset"
android:paddingBottom="@dimen/button_bar_height"
launcher:defaultScreen="2"
launcher:cellCountX="4"
launcher:cellCountY="4"
launcher:pageSpacing="@dimen/workspace_page_spacing"
launcher:scrollIndicatorPaddingLeft="@dimen/workspace_divider_padding_left"
launcher:scrollIndicatorPaddingRight="@dimen/workspace_divider_padding_right">

<!-- Workspace總共可翻轉5個頁面,一個 workspace_screen定義一個頁面布局-->
<include android:id="@+id/cell1" layout="@layout/workspace_screen" />
<include android:id="@+id/cell2" layout="@layout/workspace_screen" />
<include android:id="@+id/cell3" layout="@layout/workspace_screen" />
<include android:id="@+id/cell4" layout="@layout/workspace_screen" />
<include android:id="@+id/cell5" layout="@layout/workspace_screen" />
</com.android.launcher2.Workspace>
...
</com.android.launcher2.DragLayer>

    從上面的代碼中,可以知道,布局屬性defaultScreen、cellCountX、cellCountY、pageSpacing、scrollIndicatorPaddingLeft、scrollIndicatorPaddingRight都是以launcher:開頭的,而不是android:開頭(android:開頭是android系統中固有的布局屬性)。android和launcher分別是屬性的命名空間,在xml文件的根元素中定義,如:xmlns:android="http://schemas.android.com/apk/res/android"和xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"。其中"http://schemas.android.com/apk/res/是固定的,后面的android和com.android.launcher的分別為對應的包名。com.android.launcher就是Launcher的包名。

      也就是說,以launcher:開頭的是Launcher系統應用中自己聲明和定義的屬性,那么這些屬性是如何聲明、定義以及在代碼中利用的呢?接下來還是以id為workspace的視圖布局為例進行說明。

    --> id為workspace的視圖布局對應的是自定義視圖類Workspace,Workspace類的代碼如下:

public class Workspace extends SmoothPagedView
implements DropTarget, DragSource, DragScroller, View.OnTouchListener,
DragController.DragListener {
...
private int mDefaultPage;
//一頁面默認x軸和y軸方向上的細胞晶格數目(4x4)
private static final int DEFAULT_CELL_COUNT_X = 4;
private static final int DEFAULT_CELL_COUNT_Y = 4;
...
public Workspace(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
...
int cellCountX = DEFAULT_CELL_COUNT_X;
int cellCountY = DEFAULT_CELL_COUNT_Y;

TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.Workspace, defStyle, 0);
...
cellCountX = a.getInt(R.styleable.Workspace_cellCountX, cellCountX);
cellCountY = a.getInt(R.styleable.Workspace_cellCountY, cellCountY);
mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
a.recycle();
...
}
...
}
    Workspace的構造函數中,可以知道,TypedArray對象a是通過調用Workspace構造函數第一個參數context的obtainStyledAttributes方法創建的,obtainStyledAttributes方法的第一個參數是Workspace構造函數的第二個參數attrs(AttributeSet)對象。重點是obtainStyledAttributes的第二個參數R.styleable.Workspace,R.styleable.Workspace對應的是一個int[]數組(資源id數組)。而這種以R.styleable.xxx一般是在attrs.xml文件中聲明的。

     -->為此,跟蹤到Launcher中的attrs.xml文件(該文件所在路徑為packages/apps/Launcher2/res/values/attrs.xml),該文件中有如下代碼:

<resources>
...
<declare-styleable name="Workspace">
<!-- The first screen the workspace should display. -->
<attr name="defaultScreen" format="integer" />
<!-- The number of horizontal cells in the CellLayout -->
<attr name="cellCountX" format="integer" />
<!-- The number of vertical cells in the CellLayout -->
<attr name="cellCountY" format="integer" />
</declare-styleable>
...
</resources>
    從attrs.xml代碼中,可以知道,name="Workspace"的declare-styleable元素數組下分別有子元素defaultScreen、cellCountX、cellCountY,它們均為Launcher中自定義的布局屬性名稱,attrs.xml文件中的代碼就是對Launcher自定義布局屬性的聲明,format=“xxx”即為布局屬性的數據類型,這里的defaultScreen、cellCountX、cellCountY等自定義屬性均為integer類型。

    -->回到上文提到的Workspace的構造函數中的代碼:

        TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.Workspace, defStyle, 0);
        所以,obtainStyledAttributes方法的第二個參數R.styleable.Workspace對應的是attrs.xml文件中聲明的屬性資源數組<declare-styleable name="Workspace">;

    -->通過R.styleable.Workspace創建TypedArray對象a后,就可以調用a的getxxx函數取得在屬性資源數組Workspace中聲明的各個布局屬性的值了,如下:

        cellCountX = a.getInt(R.styleable.Workspace_cellCountX, cellCountX);
cellCountY = a.getInt(R.styleable.Workspace_cellCountY, cellCountY);
mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
     R.styleable.Workspace_cellCountX表示Workspace資源數組中聲明的cellCountX布局屬性,只要為Workspace中的聲明的布局屬性,均用Workspace+下划線+布局屬性表示。

    a.getInt(R.styleable.Workspace_cellCountX, cellCountX)方法首先會獲取布局屬性cellCountX定義的值,若該屬性的值沒有定義,則取第二個參數cellCountX的值作為默認值;在Workspace的構造函數中,cellCountX被賦值為DEFAULT_CELL_COUNT_X,而DEFAULT_CELL_COUNT_X被初始化為4(即workspace的X軸方向上可放置4個快捷圖標)。

    -->布局屬性的值一般都是在布局文件中定義的,如Workspace的構造函數中加載的布局屬性defaultScreen、cellCountX、cellCountY的值,在id為workspace的布局代碼中定義如下:

    <com.android.launcher2.Workspace
...
launcher:defaultScreen="2"
launcher:cellCountX="4"
launcher:cellCountY="4"
...
</com.android.launcher2.Workspace>
      有一個問題值得注意:在Workspace的構造函數中加載了defaultScreen、cellCountX、cellCountY自定義屬性。但是,id為workspace的視圖布局代碼中還有pageSpacing、scrollIndicatorPaddingLeft、scrollIndicatorPadding等自定義的布局屬性,這些屬性並沒有在Workspace的構造函數中加載,而是通過PagedView的構造函數來加載;Workspace繼承自SmoothPagedView,SmoothPagedView繼承自PagedView。也就是說,PagedView為Workspace的父類。所以pageSpacing、scrollIndicatorPaddingLeft、scrollIndicatorPadding這些父類中加載的屬性作為Workspace的屬性也是理所當然的了。

    -->PagedView的構造函數代碼如下:

//實現翻頁操作的抽象父類,有翻頁效果的類都需要繼承自它;
public abstract class PagedView extends ViewGroup {
...
public PagedView(Context context, AttributeSet attrs, int defStyle) {
...
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.PagedView, defStyle, 0);
mPageSpacing = a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0);
mPageLayoutPaddingTop = a.getDimensionPixelSize(
R.styleable.PagedView_pageLayoutPaddingTop, 0);
mPageLayoutPaddingBottom = a.getDimensionPixelSize(
R.styleable.PagedView_pageLayoutPaddingBottom, 0);
mPageLayoutPaddingLeft = a.getDimensionPixelSize(
R.styleable.PagedView_pageLayoutPaddingLeft, 0);
mPageLayoutPaddingRight = a.getDimensionPixelSize(
R.styleable.PagedView_pageLayoutPaddingRight, 0);
mPageLayoutWidthGap = a.getDimensionPixelSize(
R.styleable.PagedView_pageLayoutWidthGap, 0);
mPageLayoutHeightGap = a.getDimensionPixelSize(
R.styleable.PagedView_pageLayoutHeightGap, 0);
mScrollIndicatorPaddingLeft =
a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingLeft, 0);
mScrollIndicatorPaddingRight =
a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingRight, 0);
a.recycle();
...
}
...
}
    -->同樣地,我們到attrs.xml文件代碼中找到R.styleable.PagedView對應的資源數組聲明如下:

<resources>
...
<!-- PagedView specific attributes. These attributes are used to customize
a PagedView view in XML files. -->
<declare-styleable name="PagedView">
<!-- The number of horizontal cells in a page -->
<attr name="cellCountX" />
<!-- The number of vertical cells in a page -->
<attr name="cellCountY" />
<!-- A spacing override for the icons within a page -->
<attr name="pageLayoutWidthGap" format="dimension" />
<attr name="pageLayoutHeightGap" format="dimension" />
<!-- The padding of the pages that are dynamically created per page -->
<attr name="pageLayoutPaddingTop" format="dimension" />
<attr name="pageLayoutPaddingBottom" format="dimension" />
<attr name="pageLayoutPaddingLeft" format="dimension" />
<attr name="pageLayoutPaddingRight" format="dimension" />
<!-- The space between adjacent pages of the PagedView. -->
<attr name="pageSpacing" format="dimension" />
<!-- The padding for the scroll indicator area -->
<attr name="scrollIndicatorPaddingLeft" format="dimension" />
<attr name="scrollIndicatorPaddingRight" format="dimension" />
</declare-styleable>
...
</resources>
     format="dimension"表示屬性的值是在dimens.xml定義;所以,workspace布局文件中的其他自定義屬性pageSpacing、scrollIndicatorPaddingLeft、scrollIndicatorPadding是這樣通過attrs.xml文件聲明和PagedView的構造函數加載出來的。

     至於其他的Launcher自定義的布局屬性,同樣的也是通過在attrs.xml文件聲明、布局文件中定義、自定義視圖類的構造函數中加載這幾個流程。

    編寫屬於我們自己的自定義屬性:

     基於上面介紹的launcher中的自定義屬性,我們在開發過程中也可以自己去定義我們需要的屬性。通過上面的學習我們知道,自定義屬性主要有三點:

       1.在values文件下創建attrs.xml文件,在該文件里定義和聲明我們需要的屬性。

       2.在相關布局文件中定義命名空間和引用自定義的屬性賦值。

       3.在相關定義類的構造函數中獲取自定義的屬性值。


           接下來,我們基於以上三點一步步來實現如何自定義自己的屬性。

        1.創建attrs.xml文件,代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>

<declare-styleable name="MyCustomAttribute">
<attr name="myText" format="string"/>
<attr name="myTextColor" format="color"/>
<attr name="myTextSize" format="dimension"/>
</declare-styleable>

</resources>
        2.在主布局文件activity_main.xml中定義命名空間myattr和賦值自定義的屬性,代碼如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:myattr="http://schemas.android.com/apk/res/com.stevenhu.android.custom.attribute"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >

<com.stevenhu.android.custom.attribute.CustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
myattr:myText="this is my custom attribute"
myattr:myTextColor="@color/red"
myattr:myTextSize = "18sp"
android:padding="10dip"
android:layout_centerInParent="true"
/>

</RelativeLayout>
    3.在相關自定義CustomView類的構造函數中獲取自定義的屬性值,代碼如下:

package com.stevenhu.android.custom.attribute;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;

public class CustomView extends View{

//文本
private String mMyText;
//文本顏色
private int mMyTextColor;
//文本字體大小
private int mMyTestSize;
//繪制時控制文本繪制的范圍
private Rect mRect;
//畫筆
private Paint mPaint;


public CustomView(Context context) {
this(context, null);
}

public CustomView(Context context,AttributeSet attrs) {
this(context, attrs, 0);
}

public CustomView(Context context,AttributeSet attrs,int defStyleAttr) {
super(context, attrs, defStyleAttr);

//獲取自定義屬性樣式
TypedArray typeArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyCustomAttribute,
defStyleAttr, 0);

//獲取文本內容
mMyText = typeArray.getString(R.styleable.MyCustomAttribute_myText);
//獲取文本顏色
mMyTextColor = typeArray.getColor(R.styleable.MyCustomAttribute_myTextColor, Color.BLACK);
//獲取文本字體大小,默認值設為14sp
mMyTestSize = typeArray.getDimensionPixelSize(R.styleable.MyCustomAttribute_myTextSize,
(int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 14, getResources().getDisplayMetrics()));

//樣式回收
typeArray.recycle();

mPaint = new Paint();
//設置字體大小
mPaint.setTextSize(mMyTestSize);
mRect = new Rect();
//獲取文本繪制區域
mPaint.getTextBounds(mMyText, 0, mMyText.length(), mRect);
}


@Override
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {

int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height ;

//為EXACTLY時,一般是設置了明確的值或者是MATCH_PARENT
if (widthMode == MeasureSpec.EXACTLY)
{
width = widthSize;
}
//為AT_MOST時,一般為WARP_CONTENT
else
{
mPaint.setTextSize(mMyTestSize);
mPaint.getTextBounds(mMyText, 0, mMyText.length(), mRect);
float textWidth = mRect.width();
int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
width = desired;
}

if (heightMode == MeasureSpec.EXACTLY)
{
//為EXACTLY時,采用原先計算的值
height = heightSize;
} else
{
//為AT_MOST時,得根據padding、margin等邊距的值計算(如果有的話)
mPaint.setTextSize(mMyTestSize);
mPaint.getTextBounds(mMyText, 0, mMyText.length(), mRect);
float textHeight = mRect.height();
int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
height = desired;
}

setMeasuredDimension(width, height);
}

@Override
protected void onDraw(Canvas canvas) {
//繪制區域,背景色為gray
mPaint.setColor(Color.GRAY);
//getMeasuredWidth、getMeasuredHeight得到的是自定義view的寬高
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
mPaint.setColor(mMyTextColor);
//繪制文本,文本顏色為mMyTextColor。getWidth、getHeight得到的是顯示屏幕的寬高
canvas.drawText(mMyText, getWidth()/2 - mRect.width()/2, getHeight()/2 + mRect.height()/2, mPaint);
}

}
   該例子運行效果圖如下:







注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com