亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 系統 > Android > 正文

Android自定義View仿騰訊TIM下拉刷新View

2019-10-21 21:30:26
字體:
來源:轉載
供稿:網友

一 概述

自定義 ViewAndroid 開發里面的一個大學問。偶然間看到 TIM 郵箱界面的刷新 View 還挺好玩的,于是就自己動手實現了一個,先看看 TIM 里邊的效果圖:

Android,View,騰訊TIM,下拉刷新

二 需求分析

看到上面的動圖,大概也知道我們需要實現的功能:

  • 根據拖動的進度來移動小球的位置
  • 小球移動過程的動畫

三 功能實現

新建一個 RefreshView 類繼承自 View ,然后我們再在 RefreshView 里面新建一個內部實體類: Circle

來看一下 Circle類的代碼

#Cirlce.java

 class Circle { int x; int y; int r; int color; public Circle(int x, int y, int r, int color) {  this.x = x;  this.y = y;  this.r = r;  this.color = color; } }

這是一個實體類,里面提供了 x , y , r , color 屬性分別代表圓心坐標的 x值,y值,圓的半徑 r 跟顏色。
借助此類來存儲小圓球的相關屬性。

接下來就是我們平時自定義 View 經常要重寫的三大方法了,先看 onMeasure()

#RefreshView.java

 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.EXACTLY) {  setMeasuredDimension(mWidth, heightSize); } else if (widthMeasureSpec == MeasureSpec.EXACTLY && heightMeasureSpec == MeasureSpec.AT_MOST) {  setMeasuredDimension(widthSize, mHeight); } else if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {  setMeasuredDimension(widthSize, heightSize); } else {  setMeasuredDimension(mWidth, mHeight); } }

為了適配布局文件中的 wrap_content 參數,我們需要重寫此方法(此方法不是本文的研究重點,不明白的可以百度或者google一下,或者參考《Android開發藝術探索》里面的相關章節)。

接著看 onLayout() 方法:

#RefreshView.java

 @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); initContentAttr(getMeasuredWidth(), getMeasuredHeight()); resetCircles(); }

在此方法中調用了 initContentAttr() 方法來初始化內容大小與 resetCircles() 來初始化(重置)三個小球的屬性。分別看下這兩個方法:

#RefreshView.java

 private void initContentAttr(int width, int height) { mContentWidth = width - getPaddingLeft() - getPaddingRight(); mContentHeight = height - getPaddingTop() - getPaddingBottom(); }

這方法很簡單,就是進行了 padding 的處理,得出真正的布局大小。如果不處理 padding 的話那么用戶設置了 padding 將失效。再看 resetCircles():

#RefreshView.java

 public static final int STATE_ORIGIN = 0; public static final int STATE_PREPARED = 1; private int mOriginState = STATE_ORIGIN; private void resetCircles() { if (mCircles.isEmpty()) {  int x = mContentWidth / 2;  int y = mContentHeight / 2;  mGap = x - mMinRadius; //初始化相鄰圓心間的最大間距  Circle circleLeft = new Circle(x, y, mMinRadius, 0xffff7f0a);  Circle circleCenter = new Circle(x, y, mMaxRadius, Color.RED);  Circle circleRight = new Circle(x, y, mMinRadius, Color.GREEN);  mCircles.add(LEFT, circleLeft);  mCircles.add(RIGHT, circleRight);  mCircles.add(CENTER, circleCenter); } if (mOriginState == STATE_ORIGIN) {  int x = mContentWidth / 2;  int y = mContentHeight / 2;  for (int i = 0; i < mCircles.size(); i++) {  Circle circle = mCircles.get(i);  circle.x = x;  circle.y = y;  if (i == CENTER) {   circle.r = mMaxRadius;  } else {   circle.r = mMinRadius;  }  } } else {  prepareToStart(); } }

此方法用于初始化和重置小球,方法里面進行的兩個大的 if...else 語句判斷,第一個 if 用于判斷是否應該初始化小球,第二個語句則是用于判斷小球的初始化時候的形態??梢栽谕獠空{用 setOriginState() 方法來指定小球的初始化形態,如不指定,則默認為 NOMAL,即三球重合。

#RefreshView.java

 /** * 設置圓球初始狀態 * {@link #STATE_ORIGIN}為原始狀態(三個小球重合), * {@link #STATE_PREPARED}為準備好可以刷新的狀態,三個小球間距最大 */ public void setOriginState(int state) { if (state == 0) {  mOriginState = STATE_ORIGIN; } else {  mOriginState = STATE_PREPARED; } }

最后就是最有趣的方法 onDraw() 了:

#RefreshView.java

 @Override protected void onDraw(Canvas canvas) { for (Circle circle : mCircles) {  mPaint.setColor(circle.color);  canvas.drawCircle(circle.x + getPaddingLeft(), circle.y + getPaddingTop(), circle.r, mPaint); } }

這方法很簡單,就是將 mCircles 列表里面的圓畫出來而已(里面進行了 padding 的處理)。

三大方法都講完了,可是這只是畫出了幾個小圓球而已,我們需求分析里的需求還沒實現呢,上面的方法已經把 View 的基礎搭起來了,要實現這個也就不難了。接下來就是大家期待的需求實現了:

根據拖動的進度來移動小球的位置

實現代碼如下:

#RefreshView.java

 public void drag(float fraction) { if (mOriginState == STATE_PREPARED) {  return; } if (mAnimator != null && mAnimator.isRunning()) {  return; } if (fraction > 1) {  return; } mCircles.get(LEFT).x = (int) (mMinRadius + mGap * (1f - fraction)); mCircles.get(RIGHT).x = (int) (mContentWidth / 2 + mGap * fraction); postInvalidate(); }

在方法里面進行三次判斷,如果初始狀態是 STATE_PREPARED (三小球距離最大,沒必要再變動了)、動畫正在進行或者進度大于1 都不進行移動。然后修改小球的屬性,再重繪。

小球移動過程的動畫

這個是這個自定義 View 最難的部分了,需要一些數學的小運算,有點繁瑣。

我們先來理清實現動畫的邏輯,看了開篇的gif,應該可以了解到,剛準備開始動畫時,左邊的小球應該是處于最左端,中間的小球處于中間,右邊的處于最右端。我們一個個小球來分析。

  • 左邊小球:動畫開始后,左邊的小球向右移動,并且逐漸變大,直到小球運動到中點,過了中點后小球繼續往右移動,不過卻逐漸變小,到了終點后小球將消失(消失過程為先縮小再消失,下同),接著又從左邊出現(出現過程也是從小到大的漸變,下同),然后重復上述過程。
  • 中間小球:中間的小球先向右移動,逐漸縮小,然后消失,后來再從左邊出現,最后移動到中間,其間逐漸變大。后面就是重復的上述動作。
  • 右邊小球:右邊的小球則是先消失,再從左邊出現,接著移動到中間,其間逐漸變大,然后再從中點移動到末端,其間逐漸縮小。

理清小球的移動過程對代碼的實現很有幫助,我們可以分析出:

1)每個小球對于坐標系的移動特點是一樣的。

2)每個小球對于動畫的進度的移動特點是不一樣的。

聽起來好像有點拗口,我們用人話來解釋一下:

1)每個小球對于坐標系的移動特點是一樣的:左邊的小球在坐標的最左邊是先出現,然后再向右移動,那么中間和右邊的小球呢?其實是同樣的,它們在坐標軸最左邊的時候都是先出現,再向右移動,無論哪個小球,它們在坐標軸的同一點上的動作和形態應該是一致的。

2)每個小球對于動畫的進度的移動特點是不一樣的:左邊的小球在動畫剛開始時是處于最左端,而中間的小球卻在中間位置,右邊的則在最右端。當動畫開始后,比如進行了一半,這時候左邊的小球應該移動到了中點附近,而中間的確是在末端(消失),右邊的小球就會出現在中間附近。

按照上面分析的邏輯,我把動畫的總進度分為6份,為什么是6份呢?通過上面的動畫分析,知道小球應該經歷一下過程(不分時間先后):

  • 出現 (從無漸變到初始大?。?/li>
  • 從最左端移動到中點(期間變大)
  • 從中點移動到末端(期間縮?。?/li>
  • 消失 (從初始大小漸變到消失)

為了讓小球之間的間隔保持一個優美的狀態(動畫開始后小球間不會重疊,相鄰小球的間隔基本一致),就把1、4出現和消失階段分別設為 1/6 的動畫周期,中間2、3兩個階段分別占用 1/3 個動畫周期。

Android,View,騰訊TIM,下拉刷新

這樣一來,出現跟消失占用了 1/3 動畫進度,其他兩個部分分別占用了 1/3 動畫進度。舉個例子:剛開始動畫時,設最左邊的小球為 1,中間的小球為 2,最右端的小球為 3 。

當 小球1 移動到中點時,這時動畫進行了 1/3 ,那么此時的 小球2 就應該移動到末端,小球3 則剛好經歷消失和出現過程,于是應該出現于坐標軸的起點。

由此可以看到又恢復到了剛開始時候的情況(一個小球在最左,一個在中,一個在最右),只不過是顏色不同了而已。以此類推,無限循環,就可以形成優美的動畫了。

分析出這些有什么用呢?我發現用坐標來確定小球的移動實現起來會有點小問題,所以就用動畫的進度來實現,下面看具體實現。

需要實現小球的無限運動,最實用的就是用動畫來實現,這里我用了屬性動畫。先初始化 Animotor 類:

#RefreshView.java

 private void initAnimator() { ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f); animator.setDuration(1500); animator.setRepeatCount(-1); animator.setRepeatMode(ValueAnimator.RESTART); animator.setInterpolator(new LinearInterpolator()); animator.addListener(new Animator.AnimatorListener() {  @Override  public void onAnimationStart(Animator animation) {  prepareToStart(); //確保View達到可以刷新的狀態  }  @Override  public void onAnimationEnd(Animator animation) {  }  @Override  public void onAnimationCancel(Animator animation) {  }  @Override  public void onAnimationRepeat(Animator animation) {  } }); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  @Override  public void onAnimationUpdate(ValueAnimator animation) {  for (Circle circle : mCircles) {   updateCircle(circle, mCircles.indexOf(circle), animation.getAnimatedFraction());  }  postInvalidate();  } }); mAnimator = animator; }

可以看到,這是一個無限循環的動畫,如果不手動停止,它就會一直循環下去。對于 mAnimator ,還添加了一個監聽器,當開始動畫是就調用 prepareToStart() 方法,這個方法看起來是不是有點眼熟,沒錯,它就是我們上面 resetCircles() 里面判斷小球形態為 STATE_PREPARED 是調用過,此方法將確保小球達到刷新的臨界點。我們主要看看 UpdateLisener 中的 onAnimationUpdate() 方法里面的 updateCircle() 方法:

#RefreshView

 private void updateCircle(Circle circle, int index, float fraction) { float progress = fraction; //真實進度 float virtualFraction; //每個小球內部的虛擬進度 switch (index) {  case LEFT:  if (fraction < 5f / 6f) {   progress = progress + 1f / 6f;  } else {   progress = progress - 5f / 6f;  }  break;  case CENTER:  if (fraction < 0.5f) {   progress = progress + 0.5f;  } else {   progress = progress - 0.5f;  }  break;  case RIGHT:  if (fraction < 1f / 6f) {   progress += 5f / 6f;  } else {   progress -= 1f / 6f;  }  break; } if (progress <= 1f / 6f) {  virtualFraction = progress * 6;  appear(circle, virtualFraction);  return; } if (progress >= 5f / 6f) {  virtualFraction = (progress - 5f / 6f) * 6;  disappear(circle, virtualFraction);  return; } virtualFraction = (progress - 1f / 6f) * 3f / 2f; move(circle, virtualFraction); }

我用了一個 virtualFraction 來表示每個小球的虛擬進度(相當于上面坐標圖中的下值,即坐標百分比),例如當動畫的總進度為 0 時,左小球的虛擬進度就應該是 1/6+0 (默認已經經過了出現過程,消耗了 1/6),中間小球的虛擬進度為 1/6+1/3+0 = 1/2 (默認經歷了出現,移動到中間過程),最右邊小球的虛擬進度為 1/6+1/3+1/3+0 = 5/6 。然后動畫的總進度到 1/3 時,左小球的虛擬進度就為 1/2 (中間位置)......

下面再看下 move() 、appear()、disapear() 方法:

#RefreshView

 private void appear(Circle circle, float fraction) { circle.r = (int) (mMinRadius * fraction); circle.x = mMinRadius; } private void disappear(Circle circle, float fraction) { circle.r = (int) (mMinRadius * (1 - fraction)); } private void move(Circle circle, float fraction) { int difference = mMaxRadius - mMinRadius; if (fraction < 0.5) {  circle.r = (int) (mMinRadius + difference * fraction * 2); } else {  circle.r = (int) (mMaxRadius - difference * (fraction - 0.5) * 2); } circle.x = (int) (mMinRadius + mGap * 2 * fraction); }

這個三個方法都很簡單,根據坐標的占比來計算出小球的坐標跟大小。

以上就是整個 RefershView 的實現了,如果需要看源碼的可以拉到文末。

四 使用及效果

看下怎么使用:

#MainActivity

 @Override protected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.activity_main);  mRefreshView = findViewById(R.id.refresh_view);//  mRefreshView.setOriginState(RefreshView.STATE_PREPARED);  Button start = findViewById(R.id.start);  Button stop = findViewById(R.id.stop);  SeekBar seekBar = findViewById(R.id.seek_bar);  seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {   @Override   public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {    mRefreshView.drag(progress / 100f);   }   @Override   public void onStartTrackingTouch(SeekBar seekBar) {   }   @Override   public void onStopTrackingTouch(SeekBar seekBar) {   }  });  start.setOnClickListener(this);  stop.setOnClickListener(this); } @Override public void onClick(View v) {  switch (v.getId()) {   case R.id.start:    mRefreshView.start();    break;   case R.id.stop:    mRefreshView.stop();    break;  } }

效果圖:

Android,View,騰訊TIM,下拉刷新

由于錄制軟件的問題,綠色的小球顯示效果不太好,在手機或虛擬機上顯示是正常的。再看個項目里的實際運用效果:

Android,View,騰訊TIM,下拉刷新

錄屏軟件對綠色好像過敏,將就看一下吧。

此文到此就結束了,感謝閱讀,喜歡的動動小手點個贊。

Demo 地址:https://github.com/gminibird/RefreshViewTest

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對VEVB武林網的支持。


注:相關教程知識閱讀請移步到Android開發頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美一区二区大胆人体摄影专业网站| 亚洲福利视频久久| 97超级碰碰人国产在线观看| 一本一道久久a久久精品逆3p| 欧美视频在线观看 亚洲欧| 亚洲国产婷婷香蕉久久久久久| 98午夜经典影视| 5566成人精品视频免费| 26uuu另类亚洲欧美日本一| 福利视频第一区| 红桃视频成人在线观看| 欧美成人精品激情在线观看| 中文字幕日韩精品有码视频| 色哟哟入口国产精品| 亚州精品天堂中文字幕| 亚洲久久久久久久久久| 在线成人激情黄色| 国产成人啪精品视频免费网| 国产欧美一区二区三区在线看| 国产欧美中文字幕| 日韩一级黄色av| 久久久久久久国产精品视频| 欧美日韩午夜激情| 亚洲曰本av电影| 亚洲天堂影视av| 亚洲毛茸茸少妇高潮呻吟| 国产精品成人免费视频| 欧美福利视频在线观看| 日韩精品视频免费在线观看| 日韩精品日韩在线观看| 欧美日韩一区二区三区| 久久久久久高潮国产精品视| 欧美日韩在线视频一区| 国产精品亚洲欧美导航| 久久国内精品一国内精品| 欧美精品性视频| 国产午夜精品视频| 日本久久久久久久| 中文字幕亚洲综合| 国产狼人综合免费视频| 亚洲精品久久久久中文字幕欢迎你| 国产午夜精品免费一区二区三区| 亚洲欧洲国产精品| 国产精品久久久久久av福利| 国产精品美女久久久久久免费| 第一福利永久视频精品| 91最新在线免费观看| 欧美成人免费全部观看天天性色| 国产精品美女网站| 91精品国产高清自在线看超| xvideos国产精品| 国产不卡在线观看| 日韩精品中文在线观看| 国产精品va在线| 欧美在线中文字幕| 亚洲色图13p| 91po在线观看91精品国产性色| 成人av资源在线播放| 成人网在线免费看| 伊人伊成久久人综合网站| 最近的2019中文字幕免费一页| 国产精品视频免费观看www| 中文字幕视频一区二区在线有码| 日韩一级裸体免费视频| 亚洲黄色在线看| 岛国视频午夜一区免费在线观看| 伊人成人开心激情综合网| 精品国产91乱高清在线观看| 欧美日韩中文字幕在线| 亚洲国产精品成人va在线观看| 青青草精品毛片| 国产成人精品日本亚洲| 懂色av中文一区二区三区天美| 丝袜美腿精品国产二区| 久久综合色影院| 亚洲网站在线看| 欧美性猛交xxxxx免费看| 国产精品久久不能| 久久99精品久久久久久青青91| 国产区亚洲区欧美区| 欧美壮男野外gaytube| 另类专区欧美制服同性| 亚洲精品wwww| 欧美一性一乱一交一视频| 欧美色xxxx| 性色av一区二区三区| 粉嫩av一区二区三区免费野| 精品女厕一区二区三区| 日韩欧美极品在线观看| 日韩毛片在线观看| 国产成人久久久精品一区| 国产精品成人av在线| 91免费在线视频| 欧美黑人xxxⅹ高潮交| 欧美体内谢she精2性欧美| 欧美国产视频一区二区| 日本国产精品视频| 精品欧美aⅴ在线网站| 伦理中文字幕亚洲| 亚洲系列中文字幕| 啪一啪鲁一鲁2019在线视频| 97精品伊人久久久大香线蕉| 欧美性猛交xxxx| 国产剧情日韩欧美| 91免费看片网站| 欧美丰满老妇厨房牲生活| 久久久久久国产精品美女| 久久艳片www.17c.com| 色综合色综合网色综合| 欧洲亚洲妇女av| 欧美日韩国产一区二区三区| 国产999精品久久久影片官网| 91精品在线播放| 国产日韩在线视频| 久久久亚洲福利精品午夜| 成人欧美在线视频| 欧美在线视频网| 欧美成人亚洲成人日韩成人| 久久亚洲精品小早川怜子66| 亚洲一区中文字幕| 麻豆乱码国产一区二区三区| 国产偷国产偷亚洲清高网站| 亚洲精品日韩激情在线电影| 色婷婷综合成人| 国产精品日本精品| 日韩第一页在线| 欧洲精品在线视频| 日韩www在线| 久久精品国产亚洲一区二区| 日本精品视频在线观看| 日韩一区二区福利| 97国产suv精品一区二区62| 午夜精品福利电影| 久久福利视频导航| 日本久久久久久久久久久| 日韩在线精品视频| 国产欧美久久久久久| 国产精品com| 亚洲色图15p| 中文字幕视频在线免费欧美日韩综合在线看| 一区二区三区四区在线观看视频| 国产精品偷伦视频免费观看国产| 亚洲精品自拍视频| 91精品久久久久| 91精品国产自产91精品| 91牛牛免费视频| 亚洲精品日韩激情在线电影| 国产精品久久久一区| 亚洲国产精品资源| 97香蕉超级碰碰久久免费软件| 精品av在线播放| 欧美在线观看网站| 日韩国产精品视频| 欧美激情国产高清| 欧美激情欧美狂野欧美精品| 色综合91久久精品中文字幕| 亚洲小视频在线观看| 国产成人精品免费视频| 亚洲欧美国内爽妇网| 欧美黑人性生活视频| 欧美在线视频免费观看| 欧美猛交ⅹxxx乱大交视频| 欧美激情精品久久久久久久变态| 久久久久免费视频|