前一段時間陽光小強安裝了一個豆瓣客戶端,第一次打開就被這種界面風格吸引了,今天早上起來在打開豆瓣聽音樂的時候,突然產生一個念頭,來試着實現一下這種效果,打開客戶端分析了一下發現其實這種效果的實現並不是想象中的那麼難,下面我先分析一下這種效果的實現思路,然後一步步解釋實現的過程,希望大家能提出意見和建議,一起交流學習。
先給大家展示一下我的成果吧:
其實豆瓣客戶端的界面上還有其他的文字和菜單,但是這兩個的實現效果和其他幾個類似,可以作爲代表,所以就不繪製那麼多組件了。
轉載請說明出處:http://blog.csdn.net/dawanganban
一、分析界面的組成結構
有兩個和我們手機屏幕尺寸大小相等的View(分別是灰色透明度變化的背景和主界面),假設屏幕的寬和高是w和h, 屏幕的座標原點在左上角,這兩個View相對於屏幕的座標是
剛開始的座標:
灰色背景: left 0 right w top -h bottom 0
主界面:left 0 right w top 0 bottom h
滑動到最底部(d爲滑動到最底部的高度)
灰色背景: left 0 right w top -d bottom h-d
主界面: left 0 right w top h-d bottom 2h-d
接下來我們就要分析兩個大問題:
1、滑動的具體實現
從上面的圖上可以很容易的看出來,此時我們就要考慮如何實現界面上下滑動,觀察豆瓣客戶端的滑動手勢發現支持滑動加速度檢測(速度大於某值時直接從一端滑向另一端)、支持屏幕跟隨手指滑動、屏幕的Y軸方向的中間是一個恢復位置的臨界點。
從上面的分析我們基本上可以知道用到的技術有如下幾個:
(1)監聽Event_Move事件,通過scorllBy實現實時移動(跟隨手指)。
(2)判斷Event_Up時的手指位置,來判斷是否恢復到原來位置。
(3)滑動的時候判斷手指滑動的速度,來確定是否直接滑動到另一端(上端和下端)。
2、界面上元素的縮放和透明度的變化
界面上透明度變化的地方大致有這幾處,滑動時上面的灰色背景透明度漸變(從上向下滑動變透明,從下向上滑動變灰),主界面上的文字透明度變化。
縮放的控件有主界面上的圓形圖片和底部菜單(底部菜單是類似的實現,這裏不做討論),而且隨着滑動位置從水平居中向左邊移動並且變小。
透明度變化的實現其實很簡單,只需要知道當前位置相對整個屏幕座標的比例計算出來即可,現在比較難的是如何實現中間圓形圖片的縮放和位置的移動。
可以簡單的從上圖中看出大概的變化規律,我們要參考的時屏幕的TOP和LEFT來確定圓形的位置。
二、實現過程詳解
首先我們添加兩個View(灰色界面和主界面)
private void addChild(){
addTopView();
addCenterView();
}
private void addTopView(){
View view = new View(context);
view.setBackgroundColor(Color.GRAY);
mTopView = view;
addView(mTopView);
}
private void addCenterView(){
View view = new CustomCenterVIew(context);
view.setBackgroundColor(Color.WHITE);
mCenterView = view;
addView(mCenterView);
}
重寫ViewGroup,然後再onLayout中對兩個View進行佈局(初始佈局) @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mTopView.layout(0, -mViewHeight, mViewWidth, 0);
mCenterView.layout(0, 0, mViewWidth, mViewHeight);
}
我們重點來看一下對屏幕事件的處理,下面是重寫onTouchEvent方法 private float mOldY;
private VelocityTracker vTracker;
@Override
public boolean onTouchEvent(MotionEvent event) {
int disY;
int vTrackY;
float eventY = event.getY();
obtainVelocityTracker(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mOldY = eventY;
break;
case MotionEvent.ACTION_MOVE:
disY = (int)(eventY - mOldY);
if(disY > 0 && Util.getCenterViewPointY(mCenterView)
>= mViewHeight){
return true;
}
if(disY < 0 && Util.getCenterViewPointY(mCenterView) <= mViewPointY){
return true;
}
mOldY = eventY;
scrollBy(0, -disY);
break;
case MotionEvent.ACTION_UP:
vTracker.computeCurrentVelocity(1000);
vTrackY = (int) vTracker.getYVelocity();
Log.e(TAG, "vTrack = " + vTrackY);
if(Math.abs(vTrackY) > 6000){
if(vTrackY > 0){
moveToBottom();
}else{
moveToTop();
}
}else{
int disPointY = Util.getCenterViewPointY(mCenterView) - mViewPointY;
if(disPointY > mViewHeight / 2){
moveToBottom();
}else{
moveToTop();
}
}
break;
}
return true;
}
在ACTION_MOVE事件中我們做了兩個條件判斷是爲了防止滑動到最上邊後或滑動到最下端還可以繼續滑動(限定了一個滑動的範圍),然後使用scrollBy滑動響應(手機滑動)的距離。在ACTION_UP中主要做了兩件事,一個是判斷用戶的手指滑動速度是否超出了一個臨界值,如果超出則表明用戶是想滑動到底的。另一個是判斷是否手指滑動到了屏幕的中界線以外來處理滑動到底還是恢復到原來的位置。
爲了實現平滑的滑動我們在moveToBottom和moveToTop中使用了computerScroll方法來實現平滑滑動效果。關於詳細用法請參考我的另一篇博文:http://blog.csdn.net/dawanganban/article/details/23998781
接下來我們來看一下主界面View的實現,這個View其實是一個自定義View,我重寫了onDraw方法來實現透明度和大小的變化(使用系統動畫實現不了隨着手指移動實時變化的效果)具體的實現如下:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int currentNum = Util.getCenterViewPointY(this);
Log.e(TAG, "currentNum = " + currentNum);
int alpha = 255 - (int)((float)500 *
(currentNum - CustomView.BOTTOM_MENU_HEIGHT) / maxNum);
if(alpha > 0){
titleTextPaint.setAlpha(alpha);
float titleWidth = titleTextPaint.measureText(title);
canvas.drawText(title, (mViewWidth - titleWidth) / 2, TITLE_TOP_MARGIN, titleTextPaint);
}
int beginX = (int)((mViewWidth - mCenterIconBitmap.getWidth()) / 2
- (float)CENTER_ICON_LEFT_GAP * (currentNum - CustomView.BOTTOM_MENU_HEIGHT) / maxNum);
int beginY = (int)(CENTER_ICON_MARGIN -
(float)CENTER_ICON_MARGIN * currentNum / maxNum);
canvas.save();
float scale = 1.6f - (float)currentNum / maxNum;
canvas.scale(scale, scale,
beginX + mCenterIconBitmap.getWidth() / 2,
beginY + mCenterIconBitmap.getHeight() / 2);
rectf.set(beginX, beginY, beginX + mCenterIconBitmap.getWidth(),
beginY + mCenterIconBitmap.getHeight());
canvas.drawBitmap(mCenterIconBitmap, null, rectf, bitmapPaint);
canvas.restore();
}
在上面的繪製中主要是計算大小和透明度來實現繪製中間圓形和文字的效果,現在我們運行的時候可以發現並不是我們所想的可以實現我們想要的效果,這個onDraw方法根本不會隨着滑動調用,那麼怎麼辦呢?我們可以讓滑動的時候來通知自定義View的重繪(這裏可以做一些優化)。 @Override
public void computeScroll() {
super.computeScroll();
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
centerViewUpdate();
}
看以看到在computeScroll的最後一行我們調用了一個centerViewUpdate方法來實現讓自定義View和上面灰色界面透明度變化的重繪,代碼如下: private void centerViewUpdate(){
mCenterView.postInvalidate();
float alpha = 1 - (float)(Util.getCenterViewPointY(mCenterView)
- mViewPointY) / mViewHeight;
if(alpha < 0){
mTopView.setVisibility(View.GONE);
}else{
mTopView.setVisibility(View.VISIBLE);
mTopView.setAlpha(alpha);
mTopView.postInvalidate();
}
}
至此整個工作基本完成,完整源代碼請狂點右下角跳舞的小人,加羣后在羣共享中獲取,或者點此下載