Android UI設(shè)計(jì)系列之自定義ListView仿QQ空間阻尼下拉刷新和漸變菜單欄效果(8)
好久沒(méi)有寫(xiě)有關(guān)UI的博客了,剛剛翻了一下之前的博客,最近一篇有關(guān)UI的博客:Android UI設(shè)計(jì)系列之自定義Dialog實(shí)現(xiàn)各種風(fēng)格的對(duì)話框效果(7) ,實(shí)現(xiàn)各種風(fēng)格效果的對(duì)話框,在那篇博客寫(xiě)完后由于公司封閉開(kāi)發(fā)封網(wǎng)以及其它原因致使博客中斷至今,中斷這么久很是慚愧,后續(xù)我會(huì)盡量把該寫(xiě)的都補(bǔ)充出來(lái)。近來(lái)項(xiàng)目有個(gè)需求,要做個(gè)和QQ空間類似的菜單欄透明度漸變和下拉刷新帶有阻尼回彈的效果。于是花點(diǎn)時(shí)間動(dòng)手試了試,基本上達(dá)到了QQ空間的效果,截圖如下:

通過(guò)觀察QQ空間的運(yùn)行效果,發(fā)現(xiàn)當(dāng)往上滾動(dòng)時(shí)菜單欄會(huì)隨著滾動(dòng)距離的增大其透明度組件增大直到完全不透明,反之逐漸透明。當(dāng)滾動(dòng)到頂部后繼續(xù)下拉會(huì)出現(xiàn)拉升效果當(dāng)松手之后出現(xiàn)阻尼回彈效果。于是就通過(guò)重寫(xiě)ListView模仿了QQ空間的運(yùn)行效果。
實(shí)現(xiàn)QQ空間運(yùn)行效果前需要考慮兩個(gè)問(wèn)題:
1)、如何實(shí)現(xiàn)菜單欄透明度漸變
通過(guò)觀察QQ空間的運(yùn)行效果可知其菜單欄的透明度是根據(jù)滾動(dòng)距離而動(dòng)態(tài)變化的,要想實(shí)現(xiàn)透明度的變化就需要知道的ListView的滾動(dòng)距離,所以有關(guān)透明度的問(wèn)題也就轉(zhuǎn)化成了滾動(dòng)距離的問(wèn)題。
2)、如何實(shí)現(xiàn)阻尼拉升和回彈效果
要想利用ListView實(shí)現(xiàn)阻尼效果就要求ListView首先滾動(dòng)到了頂部,當(dāng)ListView滾動(dòng)到了頂部之后若繼續(xù)手動(dòng)下滑就要求其第一個(gè)Child變化來(lái)模擬下拉效果,當(dāng)手指松開(kāi)后該Child要回彈到初始狀態(tài)。
我們先看第一個(gè)問(wèn)題:要想實(shí)現(xiàn)透明度漸變就要先獲取到ListView的滾動(dòng)距離,通過(guò)滾動(dòng)距離來(lái)計(jì)算相應(yīng)的透明度。由于ListView的復(fù)用機(jī)制就決定了不能通過(guò)第一個(gè)可見(jiàn)Item的getTop()方法來(lái)得到滾動(dòng)值,所以我們可以通過(guò)HeaderView來(lái)獲取滾動(dòng)距離,因?yàn)镠eader在ListView中是不參與復(fù)用的。
下面先了解一下ListView添加HeaderView后的滾動(dòng)流程:

上圖大致畫(huà)了ListView含有HeaderView時(shí)的三個(gè)滾動(dòng)狀態(tài),狀態(tài)一可稱為初始狀態(tài)或者是恰好滾動(dòng)到最頂部狀態(tài),此時(shí)HeaderView的getTop()值為0;狀態(tài)二為L(zhǎng)istView的滾動(dòng)中狀態(tài),此時(shí)HeaderView沒(méi)有完全滾動(dòng)出ListView邊界,getTop()的返回值為負(fù)數(shù)且其絕對(duì)值范圍在0和HeaderView的高度之間;狀態(tài)三表示的是HeaderView完全滾動(dòng)出了ListView邊界,若調(diào)用getTop()得到的返回值為負(fù)數(shù)且絕對(duì)值等于HeaderView的高度(此后可理解成HeaderView一直固定在ListView的頂部)。
明白了ListView的滾動(dòng)原理,我們先嘗試實(shí)現(xiàn)漸變菜單欄的功能。首先定義自己的ListView,取名為FlexibleListView,單詞flexible是靈活的、多樣的的意思,因?yàn)槲覀兊腖istView不僅要實(shí)現(xiàn)菜單欄的透明度漸變還要實(shí)現(xiàn)阻尼效果,所以取名為FlexibleListView比較恰當(dāng)。FlexibleListView繼承ListView后需要實(shí)現(xiàn)其構(gòu)造方法,代碼如下:
public class FlexibleListView extends ListView {
public FlexibleListView(Context context) {
super(context);
}
public FlexibleListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FlexibleListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@TargetApi(21)
public FlexibleListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
}
FlexibleListView僅僅是繼承了ListView,這本質(zhì)上和ListView沒(méi)有區(qū)別。既然我們是通過(guò)給ListView添加HeaderView的方式來(lái)判斷滾動(dòng)距離,那就要獲取到HeaderView對(duì)象。怎么獲取到HeaderView對(duì)象呢?這里有個(gè)技巧,由于給ListView添加HeaderView最終是調(diào)用ListView的addHeaderView(View v, Object data, boolean isSelected)方法,所以我們可以重寫(xiě)該方法,取到添加進(jìn)來(lái)的第一個(gè)HeaderView,那怎么判斷是第一個(gè)添加進(jìn)來(lái)的HeaderView呢?因?yàn)镠eaderView的添加是有序的即先添加的先繪制。所以可以定義一個(gè)代表第一個(gè)HeaderView的屬性mHeaderView,當(dāng)調(diào)用到addHeaderView()方法時(shí)通過(guò)判斷mHeaderView的值是否為空,如果為空就賦值否則不賦值,代碼如下:
public class FlexibleListView extends ListView {
private View mHeaderView;
private int mMaxScrollHeight;
public FlexibleListView(Context context) {
super(context);
}
public FlexibleListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FlexibleListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@TargetApi(21)
public FlexibleListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public void addHeaderView(View v, Object data, boolean isSelectable) {
super.addHeaderView(v, data, isSelectable);
if(null == mHeaderView) {
mHeaderView = v;
mMaxScrollHeight = mHeaderView.getLayoutParams().height;
}
}
}
FlexibleListView中定義了mHeaderView和mMaxScrollHeight屬性,在addHeaderView()方法中對(duì)mHeaderView做非空判斷來(lái)獲取到第一個(gè)HeaderView并賦值給mHeadereView,mMaxScrollHeight表示HeaderView的最大滾動(dòng)距離,當(dāng)HeaderView的滾動(dòng)距離超過(guò)此值我們就要設(shè)置菜單欄不透明否則就更改透明度。在這里我直接使用了HeaderView的高度來(lái)表示其允許滾動(dòng)的最大距離。
現(xiàn)在可以獲取到ListView的第一個(gè)HeaderView,接下來(lái)就是判斷ListView的滾動(dòng)了,這時(shí)候有的童靴可能會(huì)想到采用給ListView添加ScrollListener的方式,這種方式是可行的,但我們這次不采用添加Listener的方式,如果你對(duì)ListView的源碼比較熟悉的話就清楚觸發(fā)OnItemScrollListener的回調(diào)時(shí)機(jī)是在AbsListView的invokeOnItemScrollListener()方法中,該方法源碼如下:
/**
* Notify our scroll listener (if there is one) of a change in scroll state
*/
void invokeOnItemScrollListener() {
if (mFastScroll != null) {
mFastScroll.onScroll(mFirstPosition, getChildCount(), mItemCount);
}
if (mOnScrollListener != null) {
mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
}
onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
}
invokeOnItemScrollListener()方法就是觸發(fā)滾動(dòng)回調(diào)的,無(wú)論我們給不給ListView設(shè)置OnItemScrollListener那該方法都會(huì)調(diào)用,細(xì)心的同學(xué)可能發(fā)現(xiàn)在該方法最后調(diào)用了View的onScrollChanged()方法,這時(shí)候你恍然大悟,我們可以重寫(xiě)該方法呀,當(dāng)ListView發(fā)生滾動(dòng)了也就調(diào)用了onScrollChange()方法,多省事呀。呵呵,恭喜你,答對(duì)了,我們今天就是采用重寫(xiě)onScrollChanged()方法并在該方法中通過(guò)判斷ListView的HeaderView的滾動(dòng)距離來(lái)設(shè)置菜單欄的透明度的。
現(xiàn)在我們清楚了ListView的滾動(dòng)時(shí)機(jī),也有了HeaderView和最大滾動(dòng)距離,接下來(lái)就是分析實(shí)現(xiàn)漸變的條件了:要實(shí)現(xiàn)漸變我們就要清楚是誰(shuí)要漸變,在我們的APP中可能是ActionBar,也可能是ToolBar,還有可能是我們自定義的一個(gè)ViewGroup來(lái)模擬的ActionBar,所以FlexibleListView得有個(gè)代表ActionBar的mActionBar屬性并對(duì)外提供一個(gè)方法bindActionBar(),該方法就表示把需要實(shí)現(xiàn)漸變的ActionBar傳遞進(jìn)來(lái),代碼如下:
public class FlexibleListView extends ListView {
private View mActionBar;
private View mHeaderView;
private int mMaxScrollHeight;
private Drawable mActionBarBackground;
public FlexibleListView(Context context) {
super(context);
}
public FlexibleListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FlexibleListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@TargetApi(21)
public FlexibleListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if(null != mActionBarBackground) {
mActionBarBackground.setAlpha(evaluateAlpha(Math.abs(mHeaderView.getTop())));
}
}
@Override
public void addHeaderView(View v, Object data, boolean isSelectable) {
super.addHeaderView(v, data, isSelectable);
if(null == mHeaderView) {
mHeaderView = v;
mMaxScrollHeight = mHeaderView.getLayoutParams().height;
}
}
private int evaluateAlpha(int t) {
if (t >= mMaxScrollHeight) {
return 255;
}
return (int) (255 * t /(float) mMaxScrollHeight);
}
public void bindActionBar(View actionBar) {
if(null != actionBar) {
mActionBar = actionBar;
mActionBarBackground = actionBar.getBackground();
if(null == mActionBarBackground) {
mActionBarBackground = new ColorDrawable(Color.TRANSPARENT);
}
mActionBarBackground.setAlpha(0);
if(Build.VERSION.SDK_INT >= 16) {
mActionBar.setBackground(mActionBarBackground);
} else {
mActionBar.setBackgroundDrawable(mActionBarBackground);
}
}
}
public void bindActionBar(ActionBar actionBar) {
if(null != actionBar) {
// TODO impl with ActionBar
// actionBar.setBackgroundDrawable();
}
}
}
FlexibleListView新增了mActionBar和mActionBarBackground屬性,mActionBar代表需要漸變的菜單欄,mActionBarBackground為菜單欄的背景。其次對(duì)外提供了重載方法bindActionBar(),參數(shù)為ActionBar的方法是空實(shí)現(xiàn),里邊添加了TODO提示符并給了setBackgroundDrawable()提示(注意ActionBar實(shí)現(xiàn)漸變需要設(shè)置WindowFeature),希望童靴們自己可以實(shí)現(xiàn)出來(lái)。
FlexibleListView中重寫(xiě)了onScrollChanged()方法,在該方法中通過(guò)獲取mHeaderView的getTop()值然后調(diào)用evaluateAlpha()方法計(jì)算出alpha值,evaluateAlpha()的計(jì)算很簡(jiǎn)單,當(dāng)滾動(dòng)值超過(guò)了最大滾動(dòng)距離mMaxScrollHeight就返回255(255表示不透明,0表示透明),否則計(jì)算出當(dāng)前滾動(dòng)值所對(duì)應(yīng)的alpha值,最后通過(guò)調(diào)用mActionBarBackground的setAlpha()來(lái)達(dá)到mActionBar的透明度變化。
現(xiàn)在實(shí)現(xiàn)菜單欄的透明度的邏輯準(zhǔn)備就緒了,我們先測(cè)試一下看看,定義菜單欄布局,代碼如下:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="@dimen/action_bar_height" android:background="#aabbcc" android:clickable="true" android:orientation="vertical" android:paddingLeft="10dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:drawableLeft="@mipmap/back" android:text="動(dòng)態(tài)" android:textColor="#b8e7fe" android:textSize="17sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="好友動(dòng)態(tài)" android:textColor="#b8e7fe" android:textSize="17sp" /> </FrameLayout>
菜單欄包含一個(gè)返回按鈕和一個(gè)標(biāo)題,并且給菜單欄設(shè)置了固定高度和背景色,然后布局我們的activity_main.xml文件,代碼如下:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout 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"> <com.llew.wb.git.qqzone.FlexibleListView android:id="@+id/flexible_list_view" android:layout_width="match_parent" android:layout_height="match_parent" android:scrollbars="none"></com.llew.wb.git.qqzone.FlexibleListView> <include android:id="@+id/custom_action_bar" layout="@layout/action_bar_layout"/> </FrameLayout>
activity_main.xml的布局文件很簡(jiǎn)單,采用FrameLayout根布局讓菜單欄懸浮在FlexibleListView上邊,然后編寫(xiě)我們的MainActivity代碼,如下所示:
public class MainActivity extends AppCompatActivity {
private FlexibleListView mListView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initGlobalParams();
}
private void initGlobalParams() {
mListView = (FlexibleListView) findViewById(R.id.flexible_list_view);
View mFlexibleHeaderView = new View(getApplicationContext());
mFlexibleHeaderView.setBackgroundColor(Color.parseColor("#bbaacc"));
int height = getResources().getDimensionPixelSize(R.dimen.header_height);
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, height);
mFlexibleHeaderView.setLayoutParams(params);
final View actionBar = findViewById(R.id.custom_action_bar);
mListView.bindActionBar(actionBar);
mListView.addHeaderView(mFlexibleHeaderView);
mListView.setAdapter(new Adapter());
}
static class Adapter extends BaseAdapter {
@Override
public int getCount() {
return 80;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView textView = new TextView(parent.getContext());
textView.setPadding(50, 50, 50, 50);
textView.setText(position + 10 + "");
return textView;
}
}
}
在MainActivity中給FlexibleListView添加了一個(gè)固定高度背景色為"#bbaacc"的Header,并把懸浮菜單欄actionBar賦值給了FlexibleListView的mActionBar,最后設(shè)置Adapter,為了測(cè)試代碼寫(xiě)的很簡(jiǎn)單,我們運(yùn)行一下程序,看看效果:

看到運(yùn)行效果好開(kāi)心呀,(*^__^*) ……透明度漸變功能達(dá)到了我們的預(yù)期,接下來(lái)開(kāi)始實(shí)現(xiàn)阻尼效果,阻尼效果就是當(dāng)ListView滾動(dòng)到了頂部此時(shí)若繼續(xù)下滑,ListView能夠繼續(xù)往下滾動(dòng)一段距離當(dāng)手指離開(kāi)屏幕后ListView要恢復(fù)原位置。為了實(shí)現(xiàn)這個(gè)功能有的童靴可能會(huì)想到重寫(xiě)有關(guān)事件傳遞的onXXXEvent()等方法,之后在MotionEvent為DOWN,MOVE,UP或者CANCEL條件下分別做邏輯判斷來(lái)實(shí)現(xiàn)阻尼效果,此方式可行,但是和今天我們的實(shí)現(xiàn)相比起來(lái)復(fù)雜了許多....
這里所實(shí)現(xiàn)阻尼效果所采用的方法是利用View的overScrollBy()方法,有的童靴可能會(huì)問(wèn)overScrollBy()方法是2.3版本之后才增加的,2.3版本之前的兼容性怎么辦?我實(shí)現(xiàn)這個(gè)功能之前也考慮過(guò)這個(gè)問(wèn)題,一方面我們公司的APP只支持3.0以上版本,另一方面2.3及以前的版本市場(chǎng)占有率幾乎微乎其微了,所以可以考慮不再兼容2.3以前的老版本。
有的同學(xué)或許對(duì)overScrollBy()方法比較陌生,先大致說(shuō)一下該方法,其源碼如下:
/**
* Scroll the view with standard behavior for scrolling beyond the normal
* content boundaries. Views that call this method should override
* {@link #onOverScrolled(int, int, boolean, boolean)} to respond to the
* results of an over-scroll operation.
*
* Views can use this method to handle any touch or fling-based scrolling.
*
* @param deltaX Change in X in pixels
* @param deltaY Change in Y in pixels
* @param scrollX Current X scroll value in pixels before applying deltaX
* @param scrollY Current Y scroll value in pixels before applying deltaY
* @param scrollRangeX Maximum content scroll range along the X axis
* @param scrollRangeY Maximum content scroll range along the Y axis
* @param maxOverScrollX Number of pixels to overscroll by in either direction
* along the X axis.
* @param maxOverScrollY Number of pixels to overscroll by in either direction
* along the Y axis.
* @param isTouchEvent true if this scroll operation is the result of a touch event.
* @return true if scrolling was clamped to an over-scroll boundary along either
* axis, false otherwise.
*/
@SuppressWarnings({"UnusedParameters"})
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY,
int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
// ......
}
閱讀源碼看注釋很重要,我們先看一下注釋,大致意思如下:
當(dāng)View組件滾動(dòng)到邊界時(shí)還會(huì)繼續(xù)進(jìn)行之前的滾動(dòng)操作(注意:沒(méi)有滾動(dòng)到邊界時(shí)是不會(huì)觸發(fā)該方法的),如果View組件調(diào)用了該方法那么View組件就應(yīng)該重寫(xiě)onOverScrolled()方法來(lái)響應(yīng)over-scroll操作。View控件可以調(diào)用該方法處理任何的觸摸滾動(dòng)或者是快速滑動(dòng)等。感覺(jué)翻譯的好別扭,說(shuō)的直白點(diǎn)就是當(dāng)ListView,ScrollView等滾動(dòng)到頭了若繼續(xù)下滑就會(huì)調(diào)用該方法。
overScrollBy()方法有9個(gè)參數(shù),每個(gè)參數(shù)注釋都說(shuō)的很詳細(xì),我們只看需要用到的倆參數(shù)deltaY和isTouchEvent;deltaY表示的是在Y軸上滾動(dòng)的相對(duì)值,比如ListView滾動(dòng)到了頂部此時(shí)如果繼續(xù)下拉,deltaY值為負(fù)數(shù),當(dāng)其滾動(dòng)到了最底部當(dāng)我們繼續(xù)上拉,deltaY值為正數(shù),所以我們可以根據(jù)deltaY判斷ListView是上拉操作還是下拉操作,isTouchEvent為true表示手指在觸摸屏幕否則離開(kāi)屏幕。
了解overScrollBy()方法后開(kāi)始實(shí)現(xiàn)阻尼效果,核心就是重寫(xiě)overScrollBy()方法,在該方法中動(dòng)態(tài)改變HeaderView的高度,若手指松開(kāi)我們就復(fù)原HeaderView。我們知道QQ空間頂部是一張圖片,當(dāng)下拉的時(shí)候該圖片有彈性拉升效果,當(dāng)手指松開(kāi)后圖片又伸縮回去了,所以我們就直接用ImageView模擬此效果。模擬圖片阻尼可以讓ImageView的寬高為MATCH_PARENT(HeaderView的高度改變之后ImageView的高度也可以隨之更改),這個(gè)時(shí)候還要設(shè)置ImageView的scaleType為CENTER_CROP(不清楚ImageView的scaleType屬性可參照我之前寫(xiě)的一篇博文:Android 源碼系列之<一>從源碼的角度深入理解ImageView的ScaleType屬性)。
現(xiàn)在開(kāi)始在FlexibleListView中重寫(xiě)overScrollBy()方法,代碼如下:
@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
if(null != mHeaderView) {
if(isTouchEvent && deltaY < 0) {
mHeaderView.getLayoutParams().height += Math.abs(deltaY / 3.0);
mHeaderView.requestLayout();
}
}
return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
}
overScrollBy()方法中我們根據(jù)deltaY值動(dòng)態(tài)的更改了mHeaderView的高度并重新布局達(dá)到更改ImageView高度的目的,注意:計(jì)算高度的時(shí)候用了deltaY除以3,此時(shí)的3表示增長(zhǎng)因子,目的是讓HeaderView緩慢的增長(zhǎng),這里可以對(duì)外提供一個(gè)方法來(lái)設(shè)置此值。
現(xiàn)在僅實(shí)現(xiàn)了HeaderView的拉升功能,但是還沒(méi)有實(shí)現(xiàn)縮放功能,因?yàn)閛verScrollBay()中實(shí)現(xiàn)的是手指觸摸的下拉,當(dāng)手指離開(kāi)屏幕后要進(jìn)行HeaderView的復(fù)原操作,所以我們可以在考慮在onTouchEvent()方法中判斷MotionEvent的類型,當(dāng)為UP或者CANCEL時(shí)就復(fù)原HeaderView,復(fù)原HeaderView不能一下子復(fù)原而是要用動(dòng)畫(huà)的方式,這樣看上去才比較自然,所以onTouchEvent()代碼如下:
@Override
public boolean onTouchEvent(MotionEvent ev) {
if(null != mHeaderView) {
int action = ev.getAction();
if(MotionEvent.ACTION_UP == action || MotionEvent.ACTION_CANCEL == action) {
resetHeaderViewHeight();
}
}
return super.onTouchEvent(ev);
}
private void resetHeaderViewHeight() {
ValueAnimator valueAnimator = ValueAnimator.ofInt(1);
valueAnimator.setDuration(700);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
final float f = animation.getAnimatedFraction();
mHeaderView.getLayoutParams().height -= f * (mHeaderView.getLayoutParams().height - mMaxScrollHeight);
mHeaderView.requestLayout();
}
});
valueAnimator.setInterpolator(new OvershootInterpolator());
valueAnimator.start();
}
HeaderView的復(fù)原動(dòng)畫(huà)我們采用了ValueAnimator,當(dāng)動(dòng)畫(huà)執(zhí)行過(guò)程中我們動(dòng)態(tài)的更改HeaderView的值來(lái)達(dá)到漸變效果。接下來(lái)布局HeaderView來(lái)模擬QQ空間,代碼如下:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="@dimen/header_height"> <ImageView android:id="@+id/iv" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:src="@mipmap/ttt" /> <LinearLayout android:layout_width="match_parent" android:layout_height="30dp" android:layout_gravity="bottom" android:background="#33333333" android:gravity="center_vertical" android:orientation="horizontal"> <TextView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="相冊(cè)" android:gravity="center" android:textColor="@android:color/white" /> <View android:layout_width="1dp" android:layout_height="20dp" android:background="#ffffff" /> <TextView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="說(shuō)說(shuō)" android:gravity="center" android:textColor="@android:color/white" /> <View android:layout_width="1dp" android:layout_height="20dp" android:background="#ffffff" /> <TextView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="個(gè)性化" android:gravity="center" android:textColor="@android:color/white" /> <View android:layout_width="1dp" android:layout_height="20dp" android:background="#ffffff" /> <TextView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="\@ 與我相關(guān)" android:gravity="center" android:textColor="@android:color/white" /> </LinearLayout> </FrameLayout>
HeaderView的布局中讓ImageView的寬高都設(shè)置成了match_parent并且把scaleType設(shè)置為centerCrop。修改MainActivity的initGlobalParams()方法,代碼如下:
void initGlobalParams() {
mListView = (FlexibleListView) findViewById(R.id.flexible_list_view);
View mFlexibleHeaderView = LayoutInflater.from(this).inflate(R.layout.flexible_header_layout, mListView, false);
AbsListView.LayoutParams params = (AbsListView.LayoutParams)mFlexibleHeaderView.getLayoutParams();
if(null == params) {
params = new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, AbsListView.LayoutParams.WRAP_CONTENT);
}
params.height = getResources().getDimensionPixelSize(R.dimen.header_height);
mFlexibleHeaderView.setLayoutParams(params);
final View actionBar = findViewById(R.id.custom_action_bar);
mListView.bindActionBar(actionBar);
mListView.addHeaderView(mFlexibleHeaderView);
mListView.setAdapter(new Adapter());
}
OK,一切都準(zhǔn)備就緒,趕緊運(yùn)行一下程序,看看效果吧(*^__^*) ……

恩,看上去效果還不錯(cuò)......
好了,有關(guān)實(shí)現(xiàn)QQ空間的阻尼下拉刷新和漸變菜單欄就結(jié)束了,主要是利用了2.3版本之后的overScrollBy()方法(如果要兼容2.3之前版本需要童靴們自己去實(shí)現(xiàn)相關(guān)邏輯);其次充分的利用了ImageView的ScaleType屬性來(lái)模擬了QQ空間圖片阻尼回彈的效果。再次感謝收看(*^__^*) ……
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android使用ExpandableListView實(shí)現(xiàn)三層嵌套折疊菜單
- Android ExpandableListView雙層嵌套實(shí)現(xiàn)三級(jí)樹(shù)形菜單
- Android編程實(shí)現(xiàn)帶有圖標(biāo)的ListView并帶有長(zhǎng)按菜單效果示例
- Android編程實(shí)現(xiàn)為L(zhǎng)istView創(chuàng)建上下文菜單(ContextMenu)的方法
- Android ListView長(zhǎng)按彈出菜單二種實(shí)現(xiàn)方式示例
- Android左右滑出菜單實(shí)例分析
- android popwindow實(shí)現(xiàn)左側(cè)彈出菜單層及PopupWindow主要方法介紹
- android底部菜單欄實(shí)現(xiàn)原理與代碼
- Android滾動(dòng)菜單ListView實(shí)例詳解
相關(guān)文章
Android鬧鈴服務(wù)AlarmManager用法深入分析
這篇文章主要介紹了Android鬧鈴服務(wù)AlarmManager用法,結(jié)合實(shí)例形式深入分析了鬧鈴服務(wù)AlarmManager的功能、原理、定義與使用方法,需要的朋友可以參考下2016-08-08
Android實(shí)現(xiàn)多線程下載文件的方法
這篇文章主要介紹了Android實(shí)現(xiàn)多線程下載文件的方法,以實(shí)例形式較為詳細(xì)的分析了Android多線程文件傳輸及合并等操作的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10
Android Flutter實(shí)現(xiàn)點(diǎn)贊效果的示例代碼
點(diǎn)贊這個(gè)動(dòng)作不得不說(shuō)在社交、短視頻等App中實(shí)在是太常見(jiàn)了。本文將利用Flutter制作出一個(gè)點(diǎn)贊動(dòng)畫(huà)效果,感興趣的小伙伴可以學(xué)習(xí)一下2022-04-04
Android APP瘦身(清除工程中沒(méi)用到的資源)詳解
這篇文章主要介紹了Android 清除工程中沒(méi)用到的資源詳解的相關(guān)資料,這里舉例說(shuō)明如何實(shí)現(xiàn),需要的朋友可以參考下2016-11-11
Android 自定義密碼輸入框?qū)崿F(xiàn)代碼
最近做個(gè)項(xiàng)目自定義密碼輸入框功能,下面小編把實(shí)現(xiàn)思路分享到腳本之家平臺(tái),需要的朋友參考下吧2018-03-03
Android系統(tǒng)添加Linux驅(qū)動(dòng)
今天小編就為大家分享一篇關(guān)于Android系統(tǒng)添加Linux驅(qū)動(dòng)的文章,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-10-10
Android使用vcard文件的方法簡(jiǎn)單實(shí)例
這篇文章主要介紹了Android使用vcard文件的方法,結(jié)合實(shí)例形式分析了Android針對(duì)vcard文件的打開(kāi)、讀取、寫(xiě)入等相關(guān)操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06
Android 應(yīng)用指定瀏覽器開(kāi)發(fā)實(shí)例
這篇文章主要介紹了Android 應(yīng)用指定瀏覽器開(kāi)發(fā)實(shí)例的相關(guān)資料,需要的朋友可以參考下2016-10-10

