Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android 自定義ScrollView實現背景圖片伸縮的實現代碼及思路

android 自定義ScrollView實現背景圖片伸縮的實現代碼及思路

編輯:關於Android編程

  

  用過多米音樂的都市知道, 這個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 事件處理 **/
 @Override
 public boolean onTouchEvent(MotionEvent ev) {
  if (inner != null) {
   commOnTouchEvent(ev);
  }
  // ture:禁止控件本身的滑動.
  if (shutTouch)
   return true;
  else
   return 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;// 孩子View

 private 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;// 上線的top
 private int line_up_bottom;// 上線的bottom

 private 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
  * 方法,也應當調用父類的方法,使該方法得以執行.
  */
 @Override
 protected void onFinishInflate() {
  if (getChildCount() > 0) {
   inner = getChildAt(0);
  }
 }

 /** touch 事件處理 **/
 @Override
 public boolean onTouchEvent(MotionEvent ev) {
  if (inner != null) {
   commOnTouchEvent(ev);
  }
  // ture:禁止控件本身的滑動.
  if (shutTouch)
   return true;
  else
   return 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可以自己根據需求進行調整.

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved