a亚洲精品_精品国产91乱码一区二区三区_亚洲精品在线免费观看视频_欧美日韩亚洲国产综合_久久久久久久久久久成人_在线区

首頁(yè) > 系統(tǒng) > Android > 正文

android 自定義ScrollView實(shí)現(xiàn)背景圖片伸縮的實(shí)現(xiàn)代碼及思路

2020-04-11 12:21:43
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

  

  用過多米音樂的都市知道, 這個(gè)UI可以上下滑動(dòng),作用嘛---無(wú)聊中可以劃劃解解悶,這被錘子公司老羅稱謂為“情懷”,其實(shí)叫“情味”更合適。嘿嘿.如今挪動(dòng)互聯(lián)網(wǎng)開展這么迅速,市場(chǎng)上已不再是那早期隨便敲個(gè)APP放上架就能具有幾十萬(wàn)用戶的階段了.近來(lái)蘋果公司,為了怕android下載量趕超蘋果商店,大勢(shì)宣稱:(第 500 億個(gè)下載應(yīng)用的用戶就能夠獲得 10,000 美元的 iTunes 禮品卡,除此之外,緊隨第 500 億以后的前 50 名用戶也可以獲得 500 美元的禮品卡.至于挪動(dòng)開展趨勢(shì),我想搞挪動(dòng)IT的人心里都比擬清楚,扯遠(yuǎn)了).其實(shí)應(yīng)用UI殊效是應(yīng)用中很大的一部分,如果同樣功能的兩款軟件,一個(gè)功能好點(diǎn)如“網(wǎng)易新聞”,另外一個(gè)略微差點(diǎn)如“新浪新聞”,用戶的你毫無(wú)疑難確定會(huì)選擇網(wǎng)易客戶端.總結(jié)就是“操作性”對(duì)于產(chǎn)品起著至關(guān)重要的因素.

    接下來(lái)我們看下如何實(shí)現(xiàn),首先聲明,這個(gè)實(shí)現(xiàn)的方式不是很好,我這里只是提出一個(gè)解決方案,大家可以根據(jù)自己的想法進(jìn)行創(chuàng)新.

    道理:RelativeLayout+自定義ScrollView.

    我們大致看下布局結(jié)構(gòu)如圖:

  

其實(shí)也沒什么技術(shù)含量,我簡(jiǎn)單介紹下:紅色代表的是背景照片,綠色的代表自定義ScrollView,粉色是代表你要編輯的透明區(qū)域.也不過多解釋,想必大家都明確,我們還是來(lái)看代碼吧。

由于屬于情懷殊效(沒有具體的回調(diào)事件要求),那么就沒有必要自定義監(jiān)聽,回調(diào)處理,我直接把要處理的UI注入到自定義控件中,這樣她方便我也方便.

在此說明一下,前面部分實(shí)現(xiàn)中有誤,但是也希望您仔細(xì)品讀,相信您必定可以學(xué)到一些知識(shí)的。

首先我們將背景圖片和頂部線條注入到該控件中。接著我們看onTouchEvent事件,因?yàn)橹潦贾两K都是她在起作用.

   

復(fù)制代碼 代碼如下:

    /***
  * 觸摸事件
  *
  * @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:
   /** 回縮動(dòng)畫 **/
   if (isNeedAnimation()) {
    animation();
   }

   isMoveing = false;
   touchY = 0;// 手指松開要?dú)w0.

   break;

  /***
   * 消除出第一次挪動(dòng)計(jì)算,因?yàn)榈谝淮螣o(wú)法得知deltaY的高度, 然而我們也要進(jìn)行初始化,就是第一次挪動(dòng)的時(shí)候讓滑動(dòng)距離歸0.
   * 以后記載精確了就正常執(zhí)行.
   */
  case MotionEvent.ACTION_MOVE:

   Log.e(TAG, "isMoveing=" + isMoveing);

   touchY = ev.getY();

   float deltaY = touchY - initTouchY;// 滑動(dòng)距離

   Log.e(TAG, "deltaY=" + deltaY);

   /** 過濾: **/
   if (deltaY < 0 && inner.getTop() <= 0) {
    return;
   }

   // 當(dāng)滾動(dòng)到最上或者最下時(shí)就不會(huì)再滾動(dòng),這時(shí)挪動(dòng)布局
   isNeedMove();

   if (isMoveing) {
    // 初始化頭部矩形
    if (normal.isEmpty()) {
     // 保存正常的布局位置
     normal.set(inner.getLeft(), inner.getTop(),
       inner.getRight(), inner.getBottom());
    }
    // 挪動(dòng)布局(手勢(shì)挪動(dòng)的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;

  }
 }


簡(jiǎn)單說明:

MotionEvent.ACTION_DOWN:觸摸摁下獲得相應(yīng)的坐標(biāo).

MotionEvent.ACTION_MOVE:

里面有個(gè)方法isNeedMove。作用:我們滑動(dòng)的是ScrollView自身呢,還是我們自己模擬的那種滑動(dòng).

復(fù)制代碼 代碼如下:

/***
  * 是不是須要挪動(dòng)布局 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的低端.這時(shí)候才返回true.
  if (scrollY == 0 || scrollY == offset) {
   isMoveing = true;
  }
 }

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

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

復(fù)制代碼 代碼如下:

/***
  * 回縮動(dòng)畫
  */
 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);

  // 開啟挪動(dòng)動(dòng)畫
  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();

  /** 動(dòng)畫執(zhí)行 **/
  if (current_Top > initTop + 50 && turnListener != null)
   turnListener.onTurn();

 }

    這里我要簡(jiǎn)單說明一下,因?yàn)槲以谶@里栽了有些時(shí)光.

    比如:我們的背景圖片本來(lái)坐標(biāo)為:(0,-190,800,300),隨著手勢(shì)挪動(dòng)到(0,-100,800,390)挪動(dòng)了90像素,那么我們的TranslateAnimation應(yīng)當(dāng)如何寫呢?我之前總以為不就是末尾坐標(biāo)指向初始坐標(biāo)不就完了,結(jié)果你會(huì)發(fā)明,動(dòng)畫基本不起作用而是一閃而過。原因呢,動(dòng)畫參數(shù)弗成以為正數(shù).或許因?yàn)閯?dòng)畫是以(0,0)為參照物吧.因此要把動(dòng)畫寫成TranslateAnimation line_up_Anim = new TranslateAnimation(0, 0,Math.abs(-190- (-100)), 0);這樣我們所須要的動(dòng)畫效果就實(shí)現(xiàn)了.

    但是新的問題又出現(xiàn)了:

    當(dāng)你下拉到必定狀態(tài)后然后漸漸向上挪動(dòng),會(huì)發(fā)明挪動(dòng)的很快(沒有回縮的反響),而挪動(dòng)到最頂部的時(shí)候突然又出現(xiàn)反彈效果。這個(gè)效果固然不是我們所須要的那種。我們所須要的效果是:下拉到必定水平,然后反過來(lái)上拉的時(shí)候要漸漸的挪動(dòng)回到原點(diǎn)(中央位置)停止。如果是上拉的話,不要出現(xiàn)反彈效果,如果是下拉松開的話,出現(xiàn)反彈效果。

    描述的有點(diǎn)亂,如果想知道具體效果的話,我提議你應(yīng)用下papa,其實(shí)海內(nèi)這些比擬優(yōu)秀的應(yīng)用UI都是抄襲國(guó)外的,如果你用facebook的話,就會(huì)發(fā)明,怎么啪啪的個(gè)人頁(yè)面長(zhǎng)的也忒像facebook了。請(qǐng)看下圖:

 

    嘿嘿,不好意思,跑題了,針對(duì)上面出現(xiàn)的問題,我簡(jiǎn)單說明一下.

    首先,比如我們手勢(shì)下拉了50像素,其實(shí)是使得自定義ScrollView的孩子也就是LinearLayout這個(gè)控件的top為50,而這個(gè)時(shí)候的getScrollY()的值仍為0,但是如果此時(shí)你停止下拉反而向上拉取的話,那么此時(shí)的getScrollY()會(huì)從0開始逐步增大,當(dāng)我們挪動(dòng)到頂部也就是將ScrollView挪動(dòng)到最底部,此時(shí)的isMoveing為true,所以你繼承上拉的話會(huì)出現(xiàn)反彈效果。

    這個(gè)問題要如何解決呢,其實(shí)也不難,但是我糾結(jié)了好長(zhǎng)時(shí)光,也走了很多多少?gòu)澛?。在這里說明一下我的瞎跑路段以及疑難:當(dāng)時(shí)我就想,getScrollY()這么不聽話,我何必非要對(duì)ScrollView的孩子進(jìn)行操作呢,為何直接不對(duì)本控件執(zhí)行l(wèi)ayout(l,t,r,b)呢,后來(lái)就照著這個(gè)邏輯進(jìn)行update,終于更改了差不多了,糾結(jié)了問題再次出現(xiàn),在你下拉的時(shí)候?qū)crollView本身執(zhí)行l(wèi)ayout(l,t,r,b)這個(gè)方法可以實(shí)現(xiàn)反彈效果,但是此時(shí)你確無(wú)法進(jìn)行滑動(dòng)了,就是ScrollView本身的滑動(dòng)無(wú)緣無(wú)故的被禁止掉了.我懷疑是layout的時(shí)候參數(shù)弄錯(cuò)了。,后來(lái)仔細(xì)修改了下發(fā)明還是弗成以滑動(dòng),然后google了半天也杳無(wú)音訊,最后固然放棄,又回到了原點(diǎn)。接著揣摩。。。算是功夫不負(fù)有心人吧,最終想到了解決方案,希望對(duì)您有幫助。

    還拿上面說到的那短話,比如我們手勢(shì)下拉了50像素,那么此時(shí)touch的距離也就是50像素,如果此時(shí)我們反向上拉的話,同樣是須要50像素回到最初的位置。說到這里我想大家都明確了。(首先我們要將操作離開,分為UP,DOWN,如果是DOWN的話,那么在下拉后執(zhí)行上拉的時(shí)候我們禁用掉自定義控件的滑動(dòng),而是通過手勢(shì)執(zhí)行l(wèi)ayout執(zhí)行這50像素.)

    上面我們看部分代碼:

   

復(fù)制代碼 代碼如下:

    /**對(duì)于初次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;
   }
   

代碼很簡(jiǎn)單,不過多解釋了,不明確的話,仔細(xì)看下源碼確定就明確了。

   

touch 事件處理:

復(fù)制代碼 代碼如下:

/** touch 事件處理 **/
 @Override
 public boolean onTouchEvent(MotionEvent ev) {
  if (inner != null) {
   commOnTouchEvent(ev);
  }
  // ture:禁止控件本身的滑動(dòng).
  if (shutTouch)
   return true;
  else
   return super.onTouchEvent(ev);

 }

說明:如果返回值為true,作用:禁止ScrollView的滑動(dòng),此時(shí)的Touch事件還存哦?。?!如果對(duì)Touch事件比擬熟悉的同窗,相信以為我有點(diǎn)空話了,哈哈,我也是個(gè)小菜鳥,也卡在這里過。

最后呢,還有個(gè)小BUG,也就是那個(gè)頂部拉線,如果你讓ScrollView慣性滑動(dòng)的話,那么你會(huì)發(fā)明,頂部線條沒有追隨挪動(dòng),其實(shí)就是因?yàn)閼T性滑動(dòng)的時(shí)候我們是獲得不到getScrollY()的值得造成的,查了半天也沒有找到相關(guān)資料,這個(gè)問題就臨時(shí)就留在這里,有時(shí)光了在續(xù)。

這里我將源碼貼出來(lái):

復(fù)制代碼 代碼如下:

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;// 點(diǎn)擊時(shí)Y坐標(biāo)

 private float deltaY;// Y軸滑動(dòng)的距離

 private float initTouchY;// 初次點(diǎn)擊的Y坐標(biāo)

 private boolean shutTouch = false;// 是不是關(guān)閉ScrollView的滑動(dòng).

 private Rect normal = new Rect();// 矩形(這里只是個(gè)形式,只是用于判斷是不是須要?jiǎng)赢?)

 private boolean isMoveing = false;// 是不是開始挪動(dòng).

 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;// 拖動(dòng)時(shí)時(shí)高度。

 private int lineUp_current_Top, lineUp_current_Bottom;// 上線

 private onTurnListener turnListener;

 private ImageView imageHeader;

 public void setImageHeader(ImageView imageHeader) {
  this.imageHeader = imageHeader;
 }

 // 狀態(tài):上部,下部,默認(rèn)
 private enum State {
  UP, DOWN, NOMAL
 };

 // 默認(rèn)狀態(tài)
 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;
 }

 /***
  * 構(gòu)造方法
  *
  * @param context
  * @param attrs
  */
 public PersonalScrollView(Context context, AttributeSet attrs) {
  super(context, attrs);
 }

 /***
  * 根據(jù) XML 生成視圖工作實(shí)現(xiàn).該函數(shù)在生成視圖的最后調(diào)用,在所有子視圖添加完以后. 即使子類覆蓋了 onFinishInflate
  * 方法,也應(yīng)當(dāng)調(diào)用父類的方法,使該方法得以執(zhí)行.
  */
 @Override
 protected void onFinishInflate() {
  if (getChildCount() > 0) {
   inner = getChildAt(0);
  }
 }

 /** touch 事件處理 **/
 @Override
 public boolean onTouchEvent(MotionEvent ev) {
  if (inner != null) {
   commOnTouchEvent(ev);
  }
  // ture:禁止控件本身的滑動(dòng).
  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:
   /** 回縮動(dòng)畫 **/
   if (isNeedAnimation()) {
    animation();
   }

   if (getScrollY() == 0) {
    state = State.NOMAL;
   }

   isMoveing = false;
   touchY = 0;
   shutTouch = false;
   break;

  /***
   * 消除出第一次挪動(dòng)計(jì)算,因?yàn)榈谝淮螣o(wú)法得知deltaY的高度, 然而我們也要進(jìn)行初始化,就是第一次挪動(dòng)的時(shí)候讓滑動(dòng)距離歸0.
   * 以后記載精確了就正常執(zhí)行.
   */
  case MotionEvent.ACTION_MOVE:

   touchY = ev.getY();
   deltaY = touchY - initTouchY;// 滑動(dòng)距離

   /** 對(duì)于初次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());
    }
    // 挪動(dòng)布局(手勢(shì)挪動(dòng)的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;

  }
 }

 /***
  * 回縮動(dòng)畫
  */
 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);

  // 開啟挪動(dòng)動(dòng)畫
  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();

  /** 動(dòng)畫執(zhí)行 **/
  if (current_Top > initTop + 50 && turnListener != null)
   turnListener.onTurn();

 }

 /** 是不是須要開啟動(dòng)畫 **/
 public boolean isNeedAnimation() {
  return !normal.isEmpty();
 }

 /***
  * 執(zhí)行翻轉(zhuǎn)
  *
  * @author jia
  *
  */
 public interface onTurnListener {

  /** 必須到達(dá)必定水平才執(zhí)行 **/
  void onTurn();
 }

}

效果圖:

 

    

 界面有點(diǎn)丑陋,不過UI可以自己根據(jù)需求進(jìn)行調(diào)整.

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 日本免费久久 | 久久久久久精 | 国产精品久久久久影院色老大 | 久久国产乱子伦精品免费午夜,浪货好紧 | 日韩精品在线观看一区二区 | 性培育学校羞耻椅子调教h 另类中文字幕 | 欧美性吧 | 性电影网站 | 亚洲v日韩v综合v精品v | 久久国产香蕉视频 | 免费在线播放av | 欧美成人精品激情在线观看 | 人人人人澡 | 国产精品美女久久久久图片 | 国产精品无码久久综合网 | 国产激情偷乱视频一区二区三区 | 亚洲一区二区三区四区在线观看 | 成人超碰在线 | 日韩视频在线观看 | 97av在线视频 | 久久成人国产视频 | 亚洲视频免费在线观看 | 日本高清久久 | 日本中文字幕在线视频 | 99在线免费视频 | 久久伊人精品视频 | 精产国产伦理一二三区 | 亚洲一级在线观看 | 国产视频久久久久久久 | 日韩二区三区 | 国产日韩欧美 | 午夜在线观看免费 | 久久国产一区 | 成人精品一区二区三区中文字幕 | 国产极品美女在线精品图片 | 日本黄网站在线观看 | 精品无码久久久久国产 | 特黄级国产片 | 精品一区二区免费视频 | 91精品国产综合久久久蜜臀图片 | 男人久久天堂 |