1、最終效果
有木有發(fā)現(xiàn)還是很小清新的感覺O(∩_∩)O~
2抬探、看整體效果這是一個scrollView酌壕,滑動時每個子view都有一個或多個動畫效果卵牍,但是如果我們直接給每個子view加上動畫去實現(xiàn)這個需求就太low了辛掠,而且也不利于擴展,所以這里將會設(shè)計一套框架猩谊,使別人能很方便的使用我們定義的控件牌捷。
3、首先看看我們是怎么使用自己設(shè)計的這個控件的
<scrollviewgroup.lly.com.scrollviewgroup.lib.DiscrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:discrollve="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<scrollviewgroup.lly.com.scrollviewgroup.lib.DiscrollViewContent
android:layout_width="match_parent"
android:layout_height="match_parent">
...
<ImageView
android:layout_width="300dp"
android:layout_height="180dp"
android:layout_gravity="center"
discrollve:discrollve_alpha="true"
discrollve:discrollve_translation="fromLeft|fromBottom"
android:src="@drawable/cheese1" />
...
</scrollviewgroup.lly.com.scrollviewgroup.lib.DiscrollViewContent>
</scrollviewgroup.lly.com.scrollviewgroup.lib.DiscrollView>
看discrollve:discrollve_alpha="true" discrollve:discrollve_translation="fromLeft|fromBottom"
這里我們給系統(tǒng)控件加上自定義屬性棒口,這樣當(dāng)別人用我們的控件剥懒,簡直不要太爽。
不過大家有沒有發(fā)現(xiàn)這是系統(tǒng)控件哎,你就這么隨隨便便的給它加個屬性崔梗,它認(rèn)識么 不報錯你就謝天謝地了 還讓它工作蒜魄,想的美旅挤。
帶著這個疑惑,我們先來看看系統(tǒng)的ViewGroup.Java類是怎么做的柒瓣。
一般我們在代碼中給布局動態(tài)添加子控件的時候都會用到addView這個方法 這里我們就跟蹤這個方法,最后發(fā)現(xiàn)他們會調(diào)用到ViewGroup的addview方法
public void addView(View child, int index) {
LayoutParams params = child.getLayoutParams();
if (params == null) {
params = generateDefaultLayoutParams();
if (params == null) {
throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
}
}
addView(child, index, params);
}
有沒有發(fā)現(xiàn)這里這里最后的params是怎么來的 不就是子控件的params么。 而addView(child, index, params); 最后會調(diào)用addViewInner 下面我們看下addViewInner是怎么做的
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
...
if (!checkLayoutParams(params)) {
params = generateLayoutParams(params);
}
if (preventRequestLayout) {
child.mLayoutParams = params;
} else {
child.setLayoutParams(params);
}
...
addInArray(child, index);
// tell our children
if (preventRequestLayout) {
child.assignParent(this);
} else {
child.mParent = this;
}
...
onViewAdded(child);
...
}
代碼還是比較多的,只關(guān)注對我們有用的片段 首先它會調(diào)用checkLayoutParams(params)
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p != null;
}
如果不等于空就會調(diào)用就調(diào)用generateLayoutParams
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return p;
}
繼續(xù)執(zhí)行
if (preventRequestLayout) {
child.mLayoutParams = params;
} else {
child.setLayoutParams(params);
}
看到上面的checkLayoutParams和generateLayoutParams方法都比較簡單而且是protected的 所以應(yīng)該是給子類實現(xiàn)的,我們看一個viewgroup的子類 LinearLayout是怎么做的
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LinearLayout.LayoutParams;
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LinearLayout.LayoutParams(getContext(), attrs);
}
看到這里就在想我們是不是也可以這么做呢,那當(dāng)然是可以的 系統(tǒng)都可以了還有什么問題甫何, 接下來我們的大波代碼來襲了
public class DiscrollViewContent extends LinearLayout {
public DiscrollViewContent(Context context) {
super(context);
setOrientation(VERTICAL);
}
public DiscrollViewContent(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(VERTICAL);
}
public DiscrollViewContent(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOrientation(VERTICAL);
}
/**
* 重寫addView
* @param child
* @param index
* @param params
*/
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
super.addView(asDiscrollvable(child,(MyLayoutParams)params), index, params);
}
private View asDiscrollvable(View child, MyLayoutParams params) {
if(!isDiscrollvable(params)){
return child;
}
DiscrollvableView discrollvableChild = new DiscrollvableView(getContext());
discrollvableChild.setDiscrollveAlpha(params.mDiscrollveAlpha);
discrollvableChild.setDiscrollveTranslation(params.mDiscrollveTranslation);
discrollvableChild.setDiscrollveScaleX(params.mDiscrollveScaleX);
discrollvableChild.setDiscrollveScaleY(params.mDiscrollveScaleY);
discrollvableChild.setDiscrollveThreshold(params.mDiscrollveThreshold);
discrollvableChild.setDiscrollveFromBgColor(params.mDiscrollveFromBgColor);
discrollvableChild.setDiscrollveToBgColor(params.mDiscrollveToBgColor);
discrollvableChild.addView(child);
return discrollvableChild;
}
/**
* 判斷是否是我們定義的LayoutParams
* @param lp
* @return
*/
private boolean isDiscrollvable(MyLayoutParams lp) {
return lp.mDiscrollveAlpha ||
lp.mDiscrollveTranslation != -1 ||
lp.mDiscrollveScaleX ||
lp.mDiscrollveScaleY ||
(lp.mDiscrollveFromBgColor != -1 && lp.mDiscrollveToBgColor != -1);
}
/**
* 重寫checkLayoutParams
* @param p
* @return
*/
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof MyLayoutParams;
}
/**
* 重寫generateDefaultLayoutParams
* @return
*/
@Override
protected LinearLayout.LayoutParams generateDefaultLayoutParams() {
return new MyLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
/**
* 重寫generateLayoutParams
* @param attrs
* @return
*/
@Override
public LinearLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MyLayoutParams(getContext(), attrs);
}
/**
* 重寫generateLayoutParams
* @param p
* @return
*/
@Override
protected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new MyLayoutParams(p.width, p.height);
}
/**
* 自定義LinearLayout.LayoutParams
*/
class MyLayoutParams extends LinearLayout.LayoutParams {
private int mDiscrollveFromBgColor;
private int mDiscrollveToBgColor;
private float mDiscrollveThreshold;
public boolean mDiscrollveAlpha;
public boolean mDiscrollveScaleX;
public boolean mDiscrollveScaleY;
private int mDiscrollveTranslation;
public MyLayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DiscrollView_LayoutParams);
try {
mDiscrollveAlpha = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_alpha, false);
mDiscrollveScaleX = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleX, false);
mDiscrollveScaleY = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleY, false);
mDiscrollveTranslation = a.getInt(R.styleable.DiscrollView_LayoutParams_discrollve_translation, -1);
mDiscrollveThreshold = a.getFloat(R.styleable.DiscrollView_LayoutParams_discrollve_threshold, 0.0f);
mDiscrollveFromBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollve_fromBgColor, -1);
mDiscrollveToBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollve_toBgColor, -1);
} finally {
a.recycle();
}
}
public MyLayoutParams(int width, int height) {
super(width, height);
}
}
}
上面這大段代碼主要就做了我們上面分析的系統(tǒng)空間
首先繼承LinearLayout, 重寫了addView亲族,generateLayoutParams,checkLayoutParams 并自定義了一個MyLayoutParams繼承自LinearLayout.LayoutParams 在addview的時候我們首先對child進行下處理,判斷子view中是否有我們定義屬性杏慰,沒有的話缘滥,就用它自己霎肯,有的話搂捧,我們在外層包一個FrameLayout,讓他執(zhí)行動畫,他的子view也將跟著執(zhí)行弱睦。
下面就是動畫的實現(xiàn)了
首先看我們的scrollView是怎么做的
private void onScrollChanged(int top) {
int scrollViewHeight = getHeight();
int scrollViewBottom = getAbsoluteBottom();
int scrollViewHalfHeight = scrollViewHeight / 2;
for(int index = 1;index<mContent.getChildCount();index++){
View child = mContent.getChildAt(index);
if(!(child instanceof DiscrollVable)){
continue;
}
DiscrollVable discrollvable = (DiscrollVable) child;
int discrollvableTop = child.getTop();
int discrollvableHeight = child.getHeight();
int discrollvableAbsoluteTop = discrollvableTop - top;
//這個view的下半部分
if(scrollViewBottom - child.getBottom() < discrollvableHeight+scrollViewHalfHeight){
//子view顯示的時候執(zhí)行
if(discrollvableAbsoluteTop <= scrollViewHeight){
int visibleGap = scrollViewHeight - discrollvableAbsoluteTop;
discrollvable.onDiscrollve(clamp(visibleGap / (float)discrollvableHeight,0.0f,1.0f));
}else {
//子view還沒顯示的時候
discrollvable.onResetDiscrollve();
}
}else{
if(discrollvableAbsoluteTop <= scrollViewHalfHeight){
int visibleGap = scrollViewHalfHeight - discrollvableAbsoluteTop;
discrollvable.onDiscrollve(clamp(visibleGap / (float)discrollvableHeight,0.0f,1.0f));
}else{
discrollvable.onResetDiscrollve();
}
}
}
}
主要就是在滑動的時候 把滑動的百分比傳給接口 ,具體由接口的實現(xiàn)類來執(zhí)行 而實現(xiàn)接口的類就是我們上面的那個FrameLayout仑嗅。
@Override
public void onDiscrollve(float ratio) {
if(ratio >= mDiscrollveThreshold) {
ratio = withThreshold(ratio);
float ratioInverse = 1 - ratio;
if(mDiscrollveAlpha) {
setAlpha(ratio);
}
if(isDiscrollveTranslationFrom(TRANSLATION_FROM_BOTTOM)) {
setTranslationY(mHeight * ratioInverse);
}
if(isDiscrollveTranslationFrom(TRANSLATION_FROM_TOP)) {
setTranslationY(-mHeight * ratioInverse);
}
if(isDiscrollveTranslationFrom(TRANSLATION_FROM_LEFT)) {
setTranslationX(-mWidth * ratioInverse);
}
if(isDiscrollveTranslationFrom(TRANSLATION_FROM_RIGHT)) {
setTranslationX(mWidth * ratioInverse);
}
if(mDiscrollveScaleX) {
setScaleX(ratio);
}
if(mDiscrollveScaleY) {
setScaleY(ratio);
}
if(mDiscrollveFromBgColor != -1 && mDiscrollveToBgColor != -1) {
setBackgroundColor((Integer) sArgbEvaluator.evaluate(ratio, mDiscrollveFromBgColor, mDiscrollveToBgColor));
}
}
}
@Override
public void onResetDiscrollve() {
if(mDiscrollveAlpha) {
setAlpha(0.0f);
}
if(isDiscrollveTranslationFrom(TRANSLATION_FROM_BOTTOM)) {
setTranslationY(mHeight);
}
if(isDiscrollveTranslationFrom(TRANSLATION_FROM_TOP)) {
setTranslationY(-mHeight);
}
if(isDiscrollveTranslationFrom(TRANSLATION_FROM_LEFT)) {
setTranslationX(-mWidth);
}
if(isDiscrollveTranslationFrom(TRANSLATION_FROM_RIGHT)) {
setTranslationX(mWidth);
}
if(mDiscrollveScaleX) {
setScaleX(0.0f);
}
if(mDiscrollveScaleY) {
setScaleY(0.0f);
}
if(mDiscrollveFromBgColor != -1 && mDiscrollveToBgColor != -1) {
setBackgroundColor(mDiscrollveFromBgColor);
}
}
代碼貼的太多了 底部將給出源碼
可以看出 每個類都不是很大,當(dāng)用戶要用的時候只要 在xml中引用我們的控件,就可以實現(xiàn)這個效果羡亩,而且他要別的效果的話同樣只要在xml中配置就好。