什麼是框架呢?肯定是給別人用的,既然要給別人用,我們就需要考慮什麼樣的框架別人纔會用,不會被罵,並且會一直用。想要做到以上這些我們的框架必須功能強大,能夠解決我們每一個人開發過程中的痛點,穩定,不會給程序帶來負面影響,使用簡單,結構清晰,易於理解,最終要的是要易於擴展等等吧。今天手寫一個動畫方面的框架玩玩,先看一下效果,如下:
效果看到了,一個滾動效果,每一個滾動出來的View都有不同的動畫,包括縮放、透明度漸變、顏色變化、移動等,具體我們要怎麼實現呢,分析一下,首先是滑動出來時開始動畫,那麼我們可以自定義ScrollView來監聽滑動,當內部子View滑動出屏幕的時候顯示動畫,但是我們都知道ScrollView只能包含一個子ViewGroup子類,這個ViewGroup裏邊可以放置多個View,那麼我們怎麼在ScrollView中操作隔一層的子View呢?這是一個問題。那麼接下來的問題是這既然是一個框架,那麼我們是要讓別人用的,別人如何爲每一個ViewGroup子類View(可能是TextView、ImageView等系統View)更方便的設置不同動畫呢?即使設置了動畫通過什麼方式去執行呢?接下來,咱們一個一個的來解決上邊的問題。
首先,如何讓用戶給每一個View設置不同的動畫?也許你會想到我們可以爲每一個子View外層再套一個自定義ViewGroup,給每一個自定義ViewGroup設置動畫就可以了,那麼在ScrollView子類ViewGroup中的代碼就會想下邊這樣:
<MyViewGrop
anitation1=""
anitation2=""
anitation3="">
<TextView />
</MyViewGrop>
<MyViewGrop
anitation1=""
anitation2=""
anitation3="">
<Button />
</MyViewGrop>
<MyViewGrop
anitation1=""
anitation2=""
anitation3="">
<Button />
</MyViewGrop>
<MyViewGrop
anitation1=""
anitation2=""
anitation3="">
<ImageView />
</MyViewGrop>
沒錯,這樣也能實現,但是是不是感覺有點坑爹呢,我們每天加一個View都需要添加一層包裝,你要知道這些都是用戶做的,那麼我們通過什麼方式可以讓用戶更簡單更方便的實現呢?我們可以嘗試一個大膽的做法:爲每一個系統控件添加自定義屬性,然後在在每一個系統控件外層動態添加一層自定義ViewGroup獲取內部系統控件的自定義屬性,複製給自定義的ViewGroup,在適當的時機給外部包裹類進行動畫操作不就可以了。那好有了想法,那就該去實現了。首先values文件夾下創建attrs文件,寫上支持的自定義動畫屬性,代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyFrameLayout">
<attr name="anitation_alpha" format="boolean" />//是否支持透明度動畫;
<attr name="anitation_scaleX" format="boolean" />//是否支持X軸縮放動畫;
<attr name="anitation_scaleY" format="boolean" />//是否支持Y軸縮放動畫;
<attr name="bgColorStart" format="color" />//背景漸變顏色的開始顏色值;
<attr name="bgColorEnd" format="color" />//背景漸變顏色的結束顏色值,與bgColorStart成對出現;
<attr name="anitation_translation">//移動動畫,是一個枚舉類型,支持上下左右四種值。
<flag name="left" value="0x01" />
<flag name="top" value="0x02" />
<flag name="right" value="0x04" />
<flag name="bottom" value="0x08" />
</attr>
</declare-styleable>
</resources>
上邊是我自定義的幾種動畫屬性,當然不僅限於此,你還可以自定義很多其他的,每一個屬性代表什麼意思,已經給出了註釋,這裏就不做過多解釋。接下來我們繼承FrameLayout來寫一個自定義控件,它的作用就是包裹每一個系統View(TextView、ImageView等),獲取系統View內部自定義屬性保存到自身,在恰當時機根據保存的值執行相應動畫。通過上邊的解釋我們知道了他裏邊需要定義成員變量來保存自定義屬性值好了,看代碼吧還是:
public class MyFrameLayout extends FrameLayout implements MyFrameLayoutAnitationCallBack {
//從哪個方向開始動畫;
private static final int TRANSLATION_LEFT = 0x01;
private static final int TRANSLATION_TOP = 0x02;
private static final int TRANSLATION_RIGHT = 0x04;
private static final int TRANSLATION_BOTTOM = 0x08;
//是否支持透明度;
private boolean mAlphaSupport;
//顏色變化的起始值;
private int mBgColorStart;
private int mBgColorEnd;
//是否支持X Y軸縮放;
private boolean mScaleXSupport;
private boolean mScaleYSupport;
//移動值;
private int mTranslationValue;
//當前View寬高;
private int mHeight, mWidth;
/**
* 顏色估值器;
*/
private static ArgbEvaluator mArgbEvaluator = new ArgbEvaluator();
public MyFrameLayout(@NonNull Context context) {
super(context);
}
public MyFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public boolean ismAlphaSupport() {
return mAlphaSupport;
}
public void setmAlphaSupport(boolean mAlphaSupport) {
this.mAlphaSupport = mAlphaSupport;
}
public int getmBgColorStart() {
return mBgColorStart;
}
public void setmBgColorStart(int mBgColorStart) {
this.mBgColorStart = mBgColorStart;
}
public int getmBgColorEnd() {
return mBgColorEnd;
}
public void setmBgColorEnd(int mBgColorEnd) {
this.mBgColorEnd = mBgColorEnd;
}
public boolean ismScaleXSupport() {
return mScaleXSupport;
}
public void setmScaleXSupport(boolean mScaleXSupport) {
this.mScaleXSupport = mScaleXSupport;
}
public boolean ismScaleYSupport() {
return mScaleYSupport;
}
public void setmScaleYSupport(boolean mScaleYSupport) {
this.mScaleYSupport = mScaleYSupport;
}
public int getmTranslationValue() {
return mTranslationValue;
}
public void setmTranslationValue(int mTranslationValue) {
this.mTranslationValue = mTranslationValue;
}
}
接下來,我們需要考慮另一個問題,就是我們什麼時候創建MyFrameLayout並讀取需要包裹的子View(系統View)的自定義屬性賦值給自己呢?我們知道按照之前的想法是想下邊這樣的:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
custom_anitation1=""
custom_anitation2=""
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
custom_anitation1=""
custom_anitation2=""
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
custom_anitation1=""
custom_anitation2=""
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
custom_anitation1=""
custom_anitation1=""
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
custom_anitation1=""
custom_anitation2=""
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
我們要讀取裏邊的custom_anitation1,custom_anitation2的值並動態生成MyFrameLayout保存這些值,我們怎麼讀取的,沒錯,如果我們需要達到這樣的效果就必須自定義外層的LinearLayout來讀取動畫值,並創建MyFrameLayout將值保存並將系統子View添加到創建的MyFrameLayout對象中,最後將MyFrameLayout對象保存到自定義的MyLinearLayout中,想法是好的,能實現嗎?來一步一步看吧,我們先要解決的問題是怎麼在外部View讀取到子View的屬性呢,系統是如何添加對象的呢?讓我們看看系統代碼是如何根據XMl創建解析View的,看哪個類呢?當然是LayoutInflater類的inflate()方法了,翻翻,你最終會找到這麼一段代碼:
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
這段代碼是關鍵,第一行通過方法名字也知道通過Tag創建對應的View,比如在XMl中定義的<TextView>,在這裏就會解析成TextView對象。第二行獲取了父佈局,第三行調用generateLayoutParams創建LayoutParams ,這裏子控件的屬性就在attrs中了,第五行將子控件添加到了父控件中,沒錯,這裏就是我們實現上述功能的關鍵所在,我們重寫generateLayoutParams(attrs)就可以拿到子控件的屬性了,通過重寫addView(view, params)我們就可以做動態添加View了,因爲用戶在定義系統View並添加自定義屬性的時候,是在LinearLayout中的,所以我們要在自定義的LinearLayout中做這些操作的,說來就來,看下邊代碼:
/**
* @Explain:自定義LinearLayout,主要功能是判斷每一個子View是否包含自定義動畫屬性,
* @1.如果包括解析自定義屬性,賦值給自定義LinearLayout.LayoutParams,創建MyFrameLayout賦值包裹子View;
* @2.不包含自定義屬性,不做處理。
* @Author:LYL
* @Version:
* @Time:2017/6/14
*/
public class MyLinearLayout extends LinearLayout {
public MyLinearLayout(Context context) {
super(context);
}
public MyLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
//返回自己定義的Params.
return new MyLayoutParams(getContext(), attrs);
}
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
//看源碼可知addView的第三個參數(params)就是我們在generateLayoutParams()方法中設置的自定義Params;
MyLayoutParams myLayoutParams = (MyLayoutParams) params;
if (myLayoutParams.isHaveMyProperty()) {
MyFrameLayout myFrameLayout = new MyFrameLayout(getContext());
myFrameLayout.addView(child);
myFrameLayout.setmAlphaSupport(myLayoutParams.mAlphaSupport);
myFrameLayout.setmScaleXSupport(myLayoutParams.mScaleXSupport);
myFrameLayout.setmScaleYSupport(myLayoutParams.mScaleYSupport);
myFrameLayout.setmBgColorStart(myLayoutParams.mBgColorStart);
myFrameLayout.setmBgColorEnd(myLayoutParams.mBgColorEnd);
myFrameLayout.setmTranslationValue(myLayoutParams.mTranslationValue);
super.addView(myFrameLayout, index, params);
} else {
super.addView(child, index, params);
}
}
//自定義LayoutParams,存儲從子控件獲取的自定義屬性。
public class MyLayoutParams extends LinearLayout.LayoutParams {
//是否支持透明度;
public boolean mAlphaSupport;
//是否支持X Y軸縮放;
public boolean mScaleXSupport;
public boolean mScaleYSupport;
//顏色變化的起始值;
public int mBgColorStart;
public int mBgColorEnd;
//移動值;
public int mTranslationValue;
public MyLayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray typedArray = c.obtainStyledAttributes(attrs, R.styleable.MyFrameLayout);
mAlphaSupport = typedArray.getBoolean(R.styleable.MyFrameLayout_anitation_alpha, false);
mBgColorStart = typedArray.getColor(R.styleable.MyFrameLayout_bgColorStart, -1);
mBgColorEnd = typedArray.getColor(R.styleable.MyFrameLayout_bgColorEnd, -1);
mScaleXSupport = typedArray.getBoolean(R.styleable.MyFrameLayout_anitation_scaleX, false);
mScaleYSupport = typedArray.getBoolean(R.styleable.MyFrameLayout_anitation_scaleY, false);
mTranslationValue = typedArray.getInt(R.styleable.MyFrameLayout_anitation_translation, -1);
typedArray.recycle();
}
}
}
在generateLayoutParams(AttributeSet attrs)中因爲返回的是一個LayoutParams,通過看源碼我們已經知道這個LayoutParams就是下邊addView(View child, int index, ViewGroup.LayoutParams params
)方法中的第三個參數,爲了更好的封裝我繼承LinearLayout.LayoutParams自定義了一個LayoutParams,所有的解析操作都在它的內部實現,看一下代碼:
//自定義LayoutParams,存儲從子控件獲取的自定義屬性。
public class MyLayoutParams extends LinearLayout.LayoutParams {
//是否支持透明度;
public boolean mAlphaSupport;
//是否支持X Y軸縮放;
public boolean mScaleXSupport;
public boolean mScaleYSupport;
//顏色變化的起始值;
public int mBgColorStart;
public int mBgColorEnd;
//移動值;
public int mTranslationValue;
public MyLayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray typedArray = c.obtainStyledAttributes(attrs, R.styleable.MyFrameLayout);
mAlphaSupport = typedArray.getBoolean(R.styleable.MyFrameLayout_anitation_alpha, false);
mBgColorStart = typedArray.getColor(R.styleable.MyFrameLayout_bgColorStart, -1);
mBgColorEnd = typedArray.getColor(R.styleable.MyFrameLayout_bgColorEnd, -1);
mScaleXSupport = typedArray.getBoolean(R.styleable.MyFrameLayout_anitation_scaleX, false);
mScaleYSupport = typedArray.getBoolean(R.styleable.MyFrameLayout_anitation_scaleY, false);
mTranslationValue = typedArray.getInt(R.styleable.MyFrameLayout_anitation_translation, -1);
typedArray.recycle();
}
/**
* 判斷當前params是否包含自定義屬性;
*
* @return
*/
public boolean isHaveMyProperty() {
if (mAlphaSupport || mScaleXSupport || mScaleYSupport || (mBgColorStart != -1 && mBgColorEnd != -1) || mTranslationValue != -1) {
return true;
}
return false;
}
}
他的內部沒什麼,就是通過generateLayoutParams(AttributeSet attrs)方法中傳的sttrs解析所有自定義進行保存,並定義了一個是否包含自定義屬性的方法。回到MyLinerLayout中的addView(View child, int index, ViewGroup.LayoutParams params)方法,我們知道這時的第三個參數就是我們的自定義LayoutParams了,因爲在它的內部如果有自定義屬性的話就會被解析,所以這裏判斷一下有沒有自定義屬性了,如果有就創建我們的MyFrameLayout並將自定義信息保存到MyFrameLayout中,最後將系統View添加到我們的MyFrameLayout中,再將MyFrameLayout添加到我們自定義的MyLinerLayout中,這樣就完成了我們最初的設想了,如果子View沒有自定義屬性的話就沒有必要包裹MyFrameLayout了,我們在代碼中確實也是這麼做的。這樣一來,用戶就只需要向下邊這樣佈局了:
<MyLinerLayout>
<TextView Android:id="tv1" custom_anitation1=""/>
<ImageView Android:id="iv1" custom_anitation1=""/>
<TextView Android:id="tv2"/>
</MyLinerLayout>
只看思路啊,細節不要在意,那麼根據我們上邊的做法,我們的tv1,iv1便會被包裹一層<MyFrameLayout>,在MyFrameLayout中保存了Custom_anitation1動畫值。而tv2不會被包裹。是不是比在用戶用起來,比上邊說的簡單了不少。做框架一定要站在用戶的角度考慮問題。現在,動畫有了,如何執行?何時執行呢?我們知道,當每一View滑出屏幕時執行動畫,那麼我們需要在自定義一個ScrollView實現它的onScrollChanged(int l, int t, int oldl, int oldt)方法,在他的內部實現滾動監聽,看代碼:
/**
* @Explain:自定義ScrollView,獲取滾動監聽,根據不同情況進行動畫;
* @Author:LYL
* @Version:
* @Time:2017/6/14
*/
public class MyScrollView extends ScrollView {
private MyLinearLayout mMyLinearLayout;
public MyScrollView(Context context) {
super(context);
}
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
//獲取內部LinearLayout;
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mMyLinearLayout = (MyLinearLayout) getChildAt(0);
}
//將第一張圖片設置成全屏;
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mMyLinearLayout.getChildAt(0).getLayoutParams().height = getHeight();
mMyLinearLayout.getChildAt(0).getLayoutParams().width = getWidth();
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
int scrollViewHeight = getHeight();
for (int i = 0; i < mMyLinearLayout.getChildCount(); i++) {
//如果子控件不是MyFrameLayout則循環下一個子控件;
View child = mMyLinearLayout.getChildAt(i);
if (!(child instanceof MyFrameLayout)) {
continue;
}
//以下爲執行動畫邏輯;
MyFrameLayoutAnitationCallBack myCallBack = (MyFrameLayoutAnitationCallBack) child;
//獲取子View高度;
int childHeight = child.getHeight();
//子控件到父控件的距離;
int childTop = child.getTop();
//滾動過程中,子View距離父控件頂部距離;
int childAbsluteTop = childTop - t;
//進入了屏幕
if (childAbsluteTop <= scrollViewHeight) {
//當前子控件顯示出來的高度;
int childShowHeight = scrollViewHeight - childAbsluteTop;
float moveRadio = childShowHeight / (float) childHeight;//這裏一定要轉化成float類型;
//執行動畫;
myCallBack.excuteAnitation(getMiddleValue(moveRadio, 0, 1));
} else {
//沒在屏幕內,恢復數據;
myCallBack.resetViewAnitation();
}
}
}
/**
* 求中間大小的值;
*
* @param radio
* @param minValue
* @param maxValue
* @return
*/
private float getMiddleValue(float radio, float minValue, float maxValue) {
return Math.max(Math.min(maxValue, radio), minValue);
}
}
這裏同時重寫了onFinishInflate(),onSizeChanged(int w, int h, int oldw, int oldh)兩個方法,在onFinishInflate()方法中我獲取到了內部的MyLinearLayout,在onSizeChanged(int w, int h, int oldw, int oldh)方法中爲了方便動畫加載,我將MyLinearLayout的第一個View設置成了全屏模式,就是那個妹子的圖片。關鍵代碼是onScrollChanged(int l, int t, int oldl, int oldt)方法,因爲有自定義方法的控件我們才包裹了MyFrameLayout,所以需要判斷一下,在這個方法中主要功能就是在恰當的時機執行動畫,這個判斷時機的代碼就不講了,都有註釋,我在這裏在調用執行動畫的時候爲了更好的體現封裝,定義了一個接口,接口裏邊有兩個方法,一個是根據當前子View(也就是包裝的MyFrameLayout)根據滑動過程中在屏幕中不同位置的百分比回調執行動畫,這個百分比就是滑動出來的部分除以當前View的高度(view.getHeight()),另一個方法就是恢復原值。那麼哪個類要實現這個接口呢,當然是哪個類執行動畫哪個類實現接口嘍,也就是我們的MyFrameLayout類,他裏邊本身就保存了執行什麼動畫等信息。重點看一下MyFrameLayout的這幾個方法:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
}
@Override
public void excuteAnitation(float moveRadio) {
//設置動畫;
if (mAlphaSupport)
setAlpha(moveRadio);
if (mScaleXSupport)
setScaleX(moveRadio);
if (mScaleYSupport)
setScaleY(moveRadio);
//從左邊移動到原位置;
if (isContainDirection(TRANSLATION_LEFT))
setTranslationX(-mWidth * (1 - moveRadio));
if (isContainDirection(TRANSLATION_TOP))
setTranslationY(-mHeight * (1 - moveRadio));
if (isContainDirection(TRANSLATION_RIGHT))
setTranslationX(mWidth * (1 - moveRadio));
if (isContainDirection(TRANSLATION_BOTTOM))
setTranslationY(mHeight * (1 - moveRadio));
if (mBgColorStart != -1 && mBgColorEnd != -1)
setBackgroundColor((int) mArgbEvaluator.evaluate(moveRadio, mBgColorStart, mBgColorEnd));
}
//
@Override
public void resetViewAnitation() {
if (mAlphaSupport)
setAlpha(0);
if (mScaleXSupport)
setScaleX(0);
if (mScaleYSupport)
setScaleY(0);
//從左邊移動到原位置;
if (isContainDirection(TRANSLATION_LEFT))
setTranslationX(-mWidth);
if (isContainDirection(TRANSLATION_TOP))
setTranslationY(-mHeight);
if (isContainDirection(TRANSLATION_RIGHT))
setTranslationX(mWidth);
if (isContainDirection(TRANSLATION_BOTTOM))
setTranslationY(mHeight);
}
private boolean isContainDirection(int direction) {
if (mTranslationValue == -1)
return false;
return (mTranslationValue & direction) == direction;
}
在onSizeChanged(intw, inth, intoldw, intoldh) 方法中獲取了當前MyFrameLayout的寬高。我們在excuteAnitation(floatmoveRadio)方法中通過判斷有哪種動畫就根據傳進來的執行動畫百分比來執行相關動畫。這裏在判斷有沒有移動動畫的其中一種的時候用到了位運算,我們在定義四個方向的時候代碼如下:
private static final int TRANSLATION_LEFT = 0x01;
private static final int TRANSLATION_TOP = 0x02;
private static final int TRANSLATION_RIGHT = 0x04;
private static final int TRANSLATION_BOTTOM = 0x08;
0x01轉換成二進制代表 0001
0x02轉換成二進制代表 0010
0x04轉換成二進制代表 0100
0x08轉換成二進制代表 1000
用戶在自定義控件賦值時比如說android:layout_gravity="left|top",那麼我們通過解析屬性時因爲是或運算,所以按照我們給的值就會得到0011;當我判斷是否有left時在isContainDirection(intdirection)方法中又與判斷的left做了與運算如0011 & 0001最後的值是0001,判斷是否等於left,通過這種方式就能判斷我們自定義屬性中是否有left值了。top、right、bottom都是一模一樣的。這個就解釋到這裏,最後在執行顏色漸變時用到了顏色估值器,也就是ArgbEvaluator類。在resetViewAnitation()方法中進行了恢復處理。好了,我們一切工作都做好了,那麼就在我們的XML中進行佈局吧,代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:lyl="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.jason.myscollanitationdemo.MyScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.jason.myscollanitationdemo.MyLinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/timg2" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/share_weixin_up"
lyl:anitation_alpha="true"
lyl:anitation_scaleX="true" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="靜夜思\n作者:李白\n牀前明月光,疑是地上霜。\n舉頭望明月,低頭思故鄉。"
android:textColor="@android:color/white"
android:textSize="17dp"
lyl:anitation_alpha="true"
lyl:anitation_translation="bottom"
lyl:bgColorEnd="#FF00FF"
lyl:bgColorStart="#7EB445" />
<ImageView
android:layout_width="200dp"
android:layout_height="180dp"
android:background="@drawable/discount"
lyl:anitation_alpha="true"
lyl:anitation_translation="left|bottom" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="念奴嬌·赤壁懷古\n
作者:蘇軾\n
大江東去,浪淘盡,千古風流人物。\n
故壘西邊,人道是,三國周郎赤壁。\n
亂石穿空,驚濤拍岸,捲起千堆雪。\n
江山如畫,一時多少豪傑。\n
遙想公瑾當年,小喬初嫁了,雄姿英發。\n
羽扇綸巾,談笑間,檣櫓灰飛煙滅。\n
故國神遊,多情應笑我,早生華髮。\n
人生如夢,一尊還酹江月。"
android:textSize="17dp"
android:gravity="center"
lyl:anitation_alpha="true"
lyl:anitation_translation="top|right"
lyl:bgColorEnd="#FFFF21"
lyl:bgColorStart="#21FF21" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/img_head_classtwo_high"
lyl:anitation_alpha="true"
lyl:anitation_translation="top" />
</com.jason.myscollanitationdemo.MyLinearLayout>
</com.jason.myscollanitationdemo.MyScrollView>
</LinearLayout>
將資源都準備好,運行就會出現上邊的結果了,好了,一個不算完美的框架就封裝好了,當然裏邊還有很多可以優化的地方。如果你想下載源碼,點擊這裏。上邊可能存在疏漏的地方,不過源碼是完整的,可以統一看一遍流程。
今天就寫到這吧,不早了,該睡了,抽時間在整理到博客吧。有什麼不足之處,還望指點一二,謝謝。