文章發佈於個人我的博客: 手動實現輪播圖(二):循環滾動、定時切換與指示器
在上一篇文章手動實現輪播圖(一):ViewPager 入門實踐中,咱們認識了ViewPager
這個佈局,也簡單上手了一下。java
接下來這篇文章,咱們會進一步朝着輪播圖的方向前進。android
原來的文章末尾,我使用了 Glide 加載 Gif 圖片做爲輪播圖的內容,因此如今也是基於那個代碼繼續下去的。git
若是對這部分比較陌生,建議回去看一下文章末尾倉庫地址裏的代碼哦github
本文章中咱們將會實現:數組
循環滾動bash
切換指示器ide
定時切換佈局
接下來就讓咱們開工吧。post
# 1. 循環滾動性能
ViewPager
雖然好用,可是並不原生支持循環滾動,也就是你:
第一個往左滑,會跳到最後一個
從最後一個往右滑,會調回第一個
咱們以前實現的效果裏,第一個就沒法再往左滑了,最後一個就沒法再往右滑了。這樣輪播圖就不是「輪」播了。
因此咱們須要本身來實現循環滾動這個效果。
該怎麼實現呢?目前也有比較成熟的三個作法:
建立不少個頁面,即使咱們真正須要展現的時候只有 5 個頁面
把起始點放在隊列中間,若是到了要展現的第一個頁面,繼續往左的時候,咱們把接下來就把頁面設置爲最後一個的樣式
這樣無論用戶往左仍是往右滑,只要是正常狀況下,用戶都是滑不到頭的,形成視覺上的循環
正常 App 中,即使你使用一個這樣的頁面隊列來顯示,用戶也沒有耐心一直滑下去
假設咱們如今建立了 1000 個頁面的ViewPager
,而後咱們實際須要展現的只有 5 個頁面,那麼實現的效果以下:
咱們把第一個展現的頁面設置爲 500,那用戶須要滑動 499 纔會到頭。
不會
由於雖說的是「建立1000」個頁面,可是實際上咱們只是告訴ViewPager
的Adapter
咱們會使用這麼多個,不表明他會建立這麼多個。
咱們會在Adapter
的getCount
方法裏返回 1000,這個方法只是幫助Adapter
獲取正確的position
的,並非真正建立出來。(經過閱讀PagerAdapter
的源碼得出)
記得前面咱們說過的,FragmentPagerAdapter
會默認幫咱們建立三個頁面,因此這裏也只會建立三個頁面,超過前中後的其餘頁面都會被回收。
咱們主要使用第一種,理解起來簡單易懂,也沒有明顯的短板。
其他兩種方法描述看下面這篇文章:Android實現真正的ViewPager【平滑過渡】...
介紹完實現思路,咱們就能夠開始實現了。
打開MainActivity.java
,修改代碼以下:
public class MainActivity extends AppCompatActivity {
private static final int MAX_NUMBER = 1000;
private static final int START_POSITION = MAX_NUMBER/2;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mViewPager.setAdapter(new FragmentPagerAdapter(fm) {
private int mIndex; // 存儲經過 position 計算出正確的數組索引
@Override
public Fragment getItem(int position) {
mIndex = Math.abs(position - START_POSITION) % mStringList.length;
if (position < START_POSITION && mIndex != 0){
mIndex = mStringList.length - mIndex;
}
return PageFragment.newInstance(mIndex);
}
@Override
public int getCount() {
return MAX_NUMBER;
}
});
mViewPager.setCurrentItem(START_POSITION);
...
}
}複製代碼
定義兩個常量,分別是
MAX_NUMBER
:頁面總數,一共 1000 個
START_POSITION
:起始的頁面,從中間第 500 個開始
mIndex = Math.abs(position - START_POSITION) % mStringList.length;
if (position < START_POSITION && mIndex != 0){
mIndex = mStringList.length - mIndex;
}複製代碼
計算當前位置position
和起始位置(START_POSITION
)的距離,而後把結果和真正要展現的頁面數量(此處暫時使用mStringList
的長度代替)取餘
距離有正負,因此取了絕對值。可是若是隻是絕對值而後去取餘的話,左滑的時候,就不是 1->5 -> 4 這樣子,而是 1 ->2 ->3 這樣。這是取餘運算的結果,不熟悉的同窗能夠回憶一下取餘的結果
因此咱們加了判斷
頁面position
大於起始位置 ,那就直接用相對距離取餘
如若小於起始位置,那麼用實際頁面數量減去取餘結果,就能夠實現倒數的效果了
@Override
public int getCount() {
return MAX_NUMBER;
}複製代碼
此處告訴Adapter
一共有多少個頁面
記得設置起始頁面哦:
mViewPager.setCurrentItem(START_POSITION);複製代碼
這樣咱們的循環滾動就完成了~快試試看吧。
許多輪播圖都有一個小指示器,用來標誌當前的頁面。咱們如今就來作一個。
作了前面的循環滾動,這樣的頁面指示器原理應該不難理解。
思路是:
建立控件樣式
選中的樣式
未選中的樣式
添加控件到視圖裏面
當頁面滑動的時候,修改指示器的樣式
在res/drawable
文件夾裏,建立兩個文件:
正常樣式:
<?xml version="1.0" encoding="utf-8" ?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size android:width="5dp"
android:height="5dp"/>
<solid android:color="@android:color/holo_red_dark"/>
</shape>複製代碼
被選中樣式:
<?xml version="1.0" encoding="utf-8" ?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size android:width="5dp"
android:height="5dp"/>
<solid android:color="@color/colorPrimary"/>
</shape>複製代碼
接着在activity_main.xml
里加入一個LinearLayout
佈局,後面咱們使用代碼的方式把小點加入進去:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.view.ViewPager
android:id="@+id/view_pager_inside"
android:layout_width="400dp"
android:layout_height="400dp"
android:background="@android:color/darker_gray"
android:layout_centerInParent="true">
</android.support.v4.view.ViewPager>
<LinearLayout
android:id="@+id/ll_inside"
android:layout_below="@+id/view_pager_inside"
android:layout_width="match_parent"
android:layout_height="30dp"
android:orientation="horizontal"
android:gravity="center"/>
</RelativeLayout>複製代碼
此處的代碼思路來自Android ViewPager 無限循環左右滑動(可自動) 實現。
回到MainActivity.java
中,
public class MainActivity extends AppCompatActivity {
private List<TextView> mTextViews;
private LinearLayout mLinearLayout;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewPager = findViewById(R.id.view_pager_inside);
mLinearLayout = findViewById(R.id.ll_inside);
initCircle();
....
}
...
private void initCircle() {
mTextViews = new ArrayList<>();
int d = 20;
int m = 7;
for (int i = 0; i < mStringList.length; i++){
TextView textView = new TextView(this);
if (i == 0){
textView.setBackgroundResource(R.drawable.dot_selected);
}else {
textView.setBackgroundResource(R.drawable.dot_normal);
}
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(d, d);
params.setMargins(m, m, m, m);
textView.setLayoutParams(params);
mTextViews.add(textView);
mLinearLayout.addView(textView);
}
}
...
}複製代碼
定義兩個變量
mTextViews
:存放小點的列表
咱們的小點實際上是由TetxView
構成,而後背景顏色設置爲圓形的
mLinearLayout
:引用剛剛建立的LinearLayout
佈局
建立一個initCIrcle()
方法
使用代碼的方式建立TextView
視圖,爲每一個視圖設置寬高、外邊距和背景等屬性
背景樣式就是剛剛建立的兩個.xml
文件
使用 addView
方法把小點添加到佈局當中
在Oncreate()
方法中調用以後,咱們就會看到小點已經出現了。
如今咱們須要根據頁面來修改樣式,以達到指示器的做用。
public class MainActivity extends AppCompatActivity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
changePoints(position % mStringList.length);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
private void changePoints(int pos){
if (mTextViews != null){
for (int i = 0; i < mTextViews.size(); i++){
if (pos == i){
mTextViews.get(i).setBackgroundResource(R.drawable.dot_selected);
}else {
mTextViews.get(i).setBackgroundResource(R.drawable.dot_normal);
}
}
}
}
}複製代碼
爲mViewPager
添加一個狀態監聽器ViewPager.OnPageChangeListener
重寫onPageSelected()
方法:該方法會在頁面被選中的時候調用
在該方法內,咱們調用changePoint()
方法來改變指示器的樣式
咱們在調用changePoint()
的時候,傳入的是position % mStringList.length
。這裏是有問題的。
若是直接使用position
對mString.length
進行取模,在這個例子裏是沒問題,由於咱們起始位置(500)剛好是mString.length
的倍數。因此此時會從 0 開始。但若是咱們之後修改了起始位置亦或者修改了展現圖片的數量的話,這裏就會出錯了。
因此咱們仍是使用和以前同樣的方式來得到索引值。修改一下onPageSelected()
方法:
private int mIndex;
@Override
public void onPageSelected(int position) {
mIndex = Math.abs(position - START_POSITION) % mStringList.length;
if (position < START_POSITION && mIndex != 0){
mIndex = mStringList.length - mIndex;
}
changePoints(mIndex);
}複製代碼
這裏爲了方便,就直接使用這段代碼了。有時間的同窗能夠本身優化一下,提升複用率。
按照道理,如今應該就能夠了。
輪播圖的其中一個特色,就是定時播放。
咱們已經實現了這麼多效果了,定時播放應該也是小菜一碟。
咱們可使用Handle
調用setCurrentItem()
便可。
如下代碼思路來自Android ViewPager 無限循環左右滑動(可自動) 實現。
修改咱們的MainActivity.java
private Handler mHandler = new Handler();
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
mHandler = new Handler();
mHandler.postDelayed(new TimerRunnable(),5000);
}
class TimerRunnable implements Runnable{
@Override
public void run() {
int curItem = mViewPager.getCurrentItem();
mViewPager.setCurrentItem(curItem+1);
if (mHandler!=null){
mHandler.postDelayed(this,5000);
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler = null; //此處在Activity退出時及時 回收
}複製代碼
調用ViewPager.setPageTransformer()
方法便可自行設置動畫。
讓咱們先新建一個動畫類PhotoTransformer.java
package me.rosuh.android.viewpagernew;
import android.support.annotation.NonNull;
import android.support.v4.view.ViewPager;
import android.view.View;
public class PhotoTransformer implements ViewPager.PageTransformer {
@Override
public void transformPage(@NonNull View page, float position) {
int pageWidth = page.getWidth();
if (position < -1){
page.setAlpha(1);
}else if (position <= 1){
page.setPivotX(position < 0f ? page.getWidth() : 0f);
page.setPivotY(page.getHeight() * 0.5f);
page.setRotationY(90f * position);
}else {
page.setAlpha(1);
}
}
}
複製代碼
而後爲mViewPager
設置動畫:
...
FragmentManager fm = getSupportFragmentManager();
mViewPager.setPageTransformer(true, new PhotoTransformer());
mViewPager.setAdapter(...)複製代碼
設置這個動畫,最好把CardView
的陰影屬性設置爲 0。而後稍微修改一下佈局。(在此不列出,能夠到代碼倉庫本身看一下)。下面是效果:
目前爲止,咱們的輪播圖就已經作好了。
這兩篇文章的目標讀者是剛入門的同窗,因此有許多地方還有改進的空間。
可是不礙於咱們掌握。
文章做者畢竟經驗很少,水平有限,因此缺漏在所不免,但願路過讀到本文的前輩們不吝賜教,謝謝~
感謝一下參考文章和資料: