android 自訂ScrollView實現背景圖片伸縮(仿多米,PaPa個人頁面特效也稱為阻尼效果)

來源:互聯網
上載者:User

首先還是按照慣例給大家看下樣本.

     

用過多米音樂的都會知道, 這個UI可以上下滑動,作用嘛---無聊中可以劃劃解解悶,這被鎚子公司老羅稱呼為“情懷”,其實叫“情趣”更合適。嘿嘿.如今移動互連網發展這麼迅速,市場上已不再是那初期隨便敲個APP放上架就能擁有幾十萬使用者的階段了.最近蘋果公司,為了怕android下載量趕超蘋果商店,大勢聲稱:(第
500 億個下載應用的使用者就可以獲得 10,000 美元的 iTunes 禮品卡,除此之外,緊隨第 500 億之後的前 50 名使用者也可以獲得 500 美元的禮品卡.至於移動發展趨勢,我想搞移動IT的人心裡都比較清楚,扯遠了).其實應用UI特效是應用中很大的一部分,如果同樣功能的兩款軟體,一個功能好點如“網易新聞”,另外一個稍微差點如“新浪新聞”,使用者的你毫無疑問肯定會選擇網易用戶端.總結就是“操作性”對於產品起著至關重要的因素.

接下來我們看下如何?,首先聲明,這個實現的方式不是很好,我這裡只是提出一個解決方案,大家可以根據自己的想法進行創新.

原理:RelativeLayout+自訂ScrollView.

我們大致看下布局結構

             

其實也沒什麼技術含量,我簡單介紹下:紅色代表的是背景照片,綠色的代表自訂ScrollView,粉色是代表你要編輯的透明地區.也不過多解釋,想必大家都明白,我們還是來看代碼吧。

由於屬於情懷特效(沒有具體的回調事件要求),那麼就沒有必要自訂監聽,回調處理,我直接把要處理的UI注入到自訂控制項中,這樣她方便我也方便.

在此說明一下,前面部分實現中有誤,但是也希望您仔細品讀,相信您一定可以學到一些知識的。


首先我們將背景圖片和頂部線條注入到該控制項中。接著我們看onTouchEvent事件,因為至始至終都是她在起作用.

/*** * 觸摸事件 *  * @param ev */public void commOnTouchEvent(MotionEvent ev) {int action = ev.getAction();switch (action) {case MotionEvent.ACTION_DOWN:initTouchY = ev.getY();current_Top = initTop = imageView.getTop();current_Bottom = initBottom = imageView.getBottom();lineUp_current_Top = line_up_top = line_up.getTop();lineUp_current_Bottom = line_up_bottom = line_up.getBottom();break;case MotionEvent.ACTION_UP:/** 回縮動畫 **/if (isNeedAnimation()) {animation();}isMoveing = false;touchY = 0;// 手指鬆開要歸0.break;/*** * 排除出第一次移動計算,因為第一次無法得知deltaY的高度, 然而我們也要進行初始化,就是第一次移動的時候讓滑動距離歸0. * 之後記錄準確了就正常執行. */case MotionEvent.ACTION_MOVE:Log.e(TAG, "isMoveing=" + isMoveing);touchY = ev.getY();float deltaY = touchY - initTouchY;// 滑動距離Log.e(TAG, "deltaY=" + deltaY);/** 過濾: **/if (deltaY < 0 && inner.getTop() <= 0) {return;}// 當滾動到最上或者最下時就不會再滾動,這時移動布局isNeedMove();if (isMoveing) {// 初始化頭部矩形if (normal.isEmpty()) {// 儲存正常的布局位置normal.set(inner.getLeft(), inner.getTop(),inner.getRight(), inner.getBottom());}// 移動布局(手勢移動的1/3)float inner_move_H = deltaY / 5;inner.layout(normal.left, (int) (normal.top + inner_move_H),normal.right, (int) (normal.bottom + inner_move_H));/** image_bg **/float image_move_H = deltaY / 10;current_Top = (int) (initTop + image_move_H);current_Bottom = (int) (initBottom + image_move_H);imageView.layout(imageView.getLeft(), current_Top,imageView.getRight(), current_Bottom);/** line_up **/float line_up_H = inner_move_H;lineUp_current_Top = (int) (line_up_top + inner_move_H);lineUp_current_Bottom = (int) (line_up_bottom + inner_move_H);line_up.layout(line_up.getLeft(), lineUp_current_Top,line_up.getRight(), lineUp_current_Bottom);}break;default:break;}}

簡單說明:

 MotionEvent.ACTION_DOWN:觸摸摁下擷取相應的座標.

MotionEvent.ACTION_MOVE:

裡面有個方法isNeedMove。作用:我們滑動的是ScrollView自身呢,還是我們自己類比的那種滑動.

/*** * 是否需要移動布局 inner.getMeasuredHeight():擷取的是控制項的總高度 *  * getHeight():擷取的是螢幕的高度 *  * @return */public void isNeedMove() {int offset = inner.getMeasuredHeight() - getHeight();int scrollY = getScrollY();// 如果ScrollView的子View們沒有超過一螢幕則scrollY == 0,直接返回true,//如果ScrollView的子View們超過了一螢幕則 getScrollY()==offset說明滑到了ScrollView的低端.這時候才返回true.if (scrollY == 0 || scrollY == offset) {isMoveing = true;}}

這裡面用到最多的就是:view.layout(l, t, r, b);作用很簡單不解釋。詳情請參看源碼.

 MotionEvent.ACTION_UP:就是做些善後操作,主要看animation方法.

/*** * 回縮動畫 */public void animation() {TranslateAnimation image_Anim = new TranslateAnimation(0, 0,Math.abs(initTop - current_Top), 0);image_Anim.setDuration(200);imageView.startAnimation(image_Anim);imageView.layout(imageView.getLeft(), (int) initTop,imageView.getRight(), (int) initBottom);// 開啟移動動畫TranslateAnimation inner_Anim = new TranslateAnimation(0, 0,inner.getTop(), normal.top);inner_Anim.setDuration(200);inner.startAnimation(inner_Anim);inner.layout(normal.left, normal.top, normal.right, normal.bottom);/** line_up **/TranslateAnimation line_up_Anim = new TranslateAnimation(0, 0,Math.abs(line_up_top - lineUp_current_Top), 0);line_up_Anim.setDuration(200);line_up.startAnimation(line_up_Anim);line_up.layout(line_up.getLeft(), line_up_top, line_up.getRight(),line_up_bottom);normal.setEmpty();/** 動畫執行 **/if (current_Top > initTop + 50 && turnListener != null)turnListener.onTurn();}

這裡我要簡單說明一下,因為我在這裡栽了有些時間.

比如:我們的背景圖片原先座標為:(0,-190,800,300),隨著手勢移動到(0,-100,800,390)移動了90像素,那麼我們的TranslateAnimation應該如何寫呢?我之前總認為不就是末尾座標指向初始座標不就完了,結果你會發現,動畫根本不起作用而是一閃而過。原因呢,動畫參數不可以為負數.或許因為動畫是以(0,0)為參照物吧.因此要把動畫寫成TranslateAnimation
line_up_Anim = new TranslateAnimation(0, 0,Math.abs(-190- (-100)), 0);這樣我們所需要的動畫效果就實現了.

但是新的問題又出現了:

當你下拉到一定狀態後然後慢慢向上移動,會發現移動的很快(沒有回縮的反應),而移動到最頂部的時候突然又出現反彈效果。這個效果固然不是我們所需要的那種。我們所需要的效果是:下拉到一定程度,然後反過來上拉的時候要慢慢的移動回到原點(中心位置)停止。如果是上拉的話,不要出現反彈效果,如果是下拉鬆開的話,出現反彈效果。

描述的有點亂,如果想知道具體效果的話,我建議你使用下papa,其實國內這些比較優秀的應用UI都是抄襲國外的,如果你用facebook的話,就會發現,怎麼啪啪的個人頁面長的也忒像facebook了。請看:

       

嘿嘿,不好意思,跑題了,針對上面出現的問題,我簡單說明一下.

首先,比如我們手勢下拉了50像素,其實是使得自訂ScrollView的孩子也就是LinearLayout這個控制項的top為50,而這個時候的getScrollY()的值仍為0,但是如果此時你停止下拉反而向上拉取的話,那麼此時的getScrollY()會從0開始逐漸增大,當我們移動到頂部也就是將ScrollView移動到最底部,此時的isMoveing為true,所以你繼續上拉的話會出現反彈效果。

這個問題要如何解決呢,其實也不難,但是我糾結了好長時間,也走了好多彎路。在這裡說明一下我的瞎跑路段以及疑問:當時我就想,getScrollY()這麼不聽話,我何必非要對ScrollView的孩子進行操作呢,為何直接不對本控制項執行layout(l,t,r,b)呢,後來就照著這個邏輯進行update,終於更改了差不多了,糾結了問題再次出現,在你下拉的時候對ScrollView本身執行layout(l,t,r,b)這個方法可以實現反彈效果,但是此時你確無法進行滑動了,就是ScrollView本身的滑動無緣無故的被禁止掉了.我懷疑是layout的時候參數弄錯了。,後來仔細修改了下發現還是不可以滑動,然後google了半天也杳無音訊,最後固然放棄,又回到了原點。接著琢磨。。。算是功夫不負有心人吧,最終想到瞭解決方案,希望對您有協助。

還拿上面說到的那短話,比如我們手勢下拉了50像素,那麼此時touch的距離也就是50像素,如果此時我們反向上拉的話,同樣是需要50像素回到最初的位置。說到這裡我想大家都明白了。(首先我們要將操作分開,分為UP,DOWN,如果是DOWN的話,那麼在下拉後執行上拉的時候我們禁用掉自訂控制項的滑動,而是通過手勢執行layout執行這50像素.)

下面我們看部分代碼:

/**對於首次Touch操作要判斷方位:UP OR DOWN**/if (deltaY < 0 && state == state.NOMAL) {state = State.UP;} else if (deltaY > 0 && state == state.NOMAL) {state = State.DOWN;}if (state == State.UP) {deltaY = deltaY < 0 ? deltaY : 0;isMoveing = false;shutTouch = false;} else if (state == state.DOWN) {if (getScrollY() <= deltaY) {shutTouch = true;isMoveing = true;}deltaY = deltaY < 0 ? 0 : deltaY;}

代碼很簡單,不過多解釋了,不明白的話,仔細看下源碼肯定就明白了。

touch 事件處理:

/** touch 事件處理 **/@Overridepublic boolean onTouchEvent(MotionEvent ev) {if (inner != null) {commOnTouchEvent(ev);}// ture:禁止控制項本身的滑動.if (shutTouch)return true;elsereturn super.onTouchEvent(ev);}

說明:如果傳回值為true,作用:禁止ScrollView的滑動,此時的Touch事件還存哦!!!如果對Touch事件比較熟悉的同學,相信覺得我有點廢話了,哈哈,我也是個小菜鳥,也卡在這裡過。


最後呢,還有個小BUG,也就是那個頂部拉線,如果你讓ScrollView慣性滑動的話,那麼你會發現,頂部線條沒有跟隨移動,其實就是因為慣性滑動的時候我們是擷取不到getScrollY()的值得造成的,查了半天也沒有找到相關資料,這個問題就暫時就留在這裡,有時間了在續。


這裡我將源碼貼出來:

package com.example.scrollviewdemo;import android.content.Context;import android.graphics.Rect;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.View;import android.view.animation.TranslateAnimation;import android.widget.ImageView;import android.widget.ScrollView;/** * 自訂ScrollView *  * @author jia *  */public class PersonalScrollView extends ScrollView {private final String TAG = PersonalScrollView.class.getSimpleName();private View inner;// 孩子Viewprivate float touchY;// 點擊時Y座標private float deltaY;// Y軸滑動的距離private float initTouchY;// 首次點擊的Y座標private boolean shutTouch = false;// 是否關閉ScrollView的滑動.private Rect normal = new Rect();// 矩形(這裡只是個形式,只是用於判斷是否需要動畫.)private boolean isMoveing = false;// 是否開始移動.private ImageView imageView;// 背景圖控制項.private View line_up;// 上線private int line_up_top;// 上線的topprivate int line_up_bottom;// 上線的bottomprivate int initTop, initBottom;// 初始高度private int current_Top, current_Bottom;// 拖動時時高度。private int lineUp_current_Top, lineUp_current_Bottom;// 上線private onTurnListener turnListener;private ImageView imageHeader;public void setImageHeader(ImageView imageHeader) {this.imageHeader = imageHeader;}// 狀態:上部,下部,預設private enum State {UP, DOWN, NOMAL};// 預設狀態private State state = State.NOMAL;public void setTurnListener(onTurnListener turnListener) {this.turnListener = turnListener;}public void setLine_up(View line_up) {this.line_up = line_up;}// 注入背景圖public void setImageView(ImageView imageView) {this.imageView = imageView;}/*** * 構造方法 *  * @param context * @param attrs */public PersonalScrollView(Context context, AttributeSet attrs) {super(context, attrs);}/*** * 根據 XML 產生視圖工作完成.該函數在產生視圖的最後調用,在所有子視圖添加完之後. 即使子類覆蓋了 onFinishInflate * 方法,也應該調用父類的方法,使該方法得以執行. */@Overrideprotected void onFinishInflate() {if (getChildCount() > 0) {inner = getChildAt(0);}}/** touch 事件處理 **/@Overridepublic boolean onTouchEvent(MotionEvent ev) {if (inner != null) {commOnTouchEvent(ev);}// ture:禁止控制項本身的滑動.if (shutTouch)return true;elsereturn super.onTouchEvent(ev);}/*** * 觸摸事件 *  * @param ev */public void commOnTouchEvent(MotionEvent ev) {int action = ev.getAction();switch (action) {case MotionEvent.ACTION_DOWN:initTouchY = ev.getY();current_Top = initTop = imageView.getTop();current_Bottom = initBottom = imageView.getBottom();if (line_up_top == 0) {lineUp_current_Top = line_up_top = line_up.getTop();lineUp_current_Bottom = line_up_bottom = line_up.getBottom();}break;case MotionEvent.ACTION_UP:/** 回縮動畫 **/if (isNeedAnimation()) {animation();}if (getScrollY() == 0) {state = State.NOMAL;}isMoveing = false;touchY = 0;shutTouch = false;break;/*** * 排除出第一次移動計算,因為第一次無法得知deltaY的高度, 然而我們也要進行初始化,就是第一次移動的時候讓滑動距離歸0. * 之後記錄準確了就正常執行. */case MotionEvent.ACTION_MOVE:touchY = ev.getY();deltaY = touchY - initTouchY;// 滑動距離/** 對於首次Touch操作要判斷方位:UP OR DOWN **/if (deltaY < 0 && state == state.NOMAL) {state = State.UP;} else if (deltaY > 0 && state == state.NOMAL) {state = State.DOWN;}if (state == State.UP) {deltaY = deltaY < 0 ? deltaY : 0;isMoveing = false;shutTouch = false;/** line_up **/lineUp_current_Top = (int) (line_up_top - getScrollY());lineUp_current_Bottom = (int) (line_up_bottom - getScrollY());Log.e(TAG, "top=" + getScrollY());line_up.layout(line_up.getLeft(), lineUp_current_Top,line_up.getRight(), lineUp_current_Bottom);} else if (state == state.DOWN) {if (getScrollY() <= deltaY) {shutTouch = true;isMoveing = true;}deltaY = deltaY < 0 ? 0 : deltaY;}if (isMoveing) {// 初始化頭部矩形if (normal.isEmpty()) {// 儲存正常的布局位置normal.set(inner.getLeft(), inner.getTop(),inner.getRight(), inner.getBottom());}// 移動布局(手勢移動的1/3)float inner_move_H = deltaY / 5;inner.layout(normal.left, (int) (normal.top + inner_move_H),normal.right, (int) (normal.bottom + inner_move_H));/** image_bg **/float image_move_H = deltaY / 10;current_Top = (int) (initTop + image_move_H);current_Bottom = (int) (initBottom + image_move_H);imageView.layout(imageView.getLeft(), current_Top,imageView.getRight(), current_Bottom);/** line_up **/lineUp_current_Top = (int) (line_up_top + inner_move_H);lineUp_current_Bottom = (int) (line_up_bottom + inner_move_H);line_up.layout(line_up.getLeft(), lineUp_current_Top,line_up.getRight(), lineUp_current_Bottom);}break;default:break;}}/*** * 回縮動畫 */public void animation() {TranslateAnimation image_Anim = new TranslateAnimation(0, 0,Math.abs(initTop - current_Top), 0);image_Anim.setDuration(200);imageView.startAnimation(image_Anim);imageView.layout(imageView.getLeft(), (int) initTop,imageView.getRight(), (int) initBottom);// 開啟移動動畫TranslateAnimation inner_Anim = new TranslateAnimation(0, 0,inner.getTop(), normal.top);inner_Anim.setDuration(200);inner.startAnimation(inner_Anim);inner.layout(normal.left, normal.top, normal.right, normal.bottom);/** line_up **/TranslateAnimation line_up_Anim = new TranslateAnimation(0, 0,Math.abs(line_up_top - lineUp_current_Top), 0);line_up_Anim.setDuration(200);line_up.startAnimation(line_up_Anim);line_up.layout(line_up.getLeft(), line_up_top, line_up.getRight(),line_up_bottom);normal.setEmpty();/** 動畫執行 **/if (current_Top > initTop + 50 && turnListener != null)turnListener.onTurn();}/** 是否需要開啟動畫 **/public boolean isNeedAnimation() {return !normal.isEmpty();}/*** * 執行翻轉 *  * @author jia *  */public interface onTurnListener {/** 必須達到一定程度才執行 **/void onTurn();}}

                   

介面有點醜陋,不過UI可以自己根據需求進行調整.


最後我在多侃一點,這裡我用的是TableLayout布局,不是ListView,因為ListView和ScrollView本身就有衝突,雖說有解決方案,但是我還是喜歡用TableLayout。代碼裡面類比了3D旋轉效果,這裡就不解釋了,網上相關文章也有好多。就說到這裡,將源碼供出,如果有問題請留言。


源碼下載

很抱歉,上面上傳的源碼是錯誤的,不過也是最初的思路,代碼比較淩亂,你們可以直接下載下面這個版本.


源碼下載

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.