Android利用ViewPager實現(xiàn)可滑動放大縮小畫廊效果
畫廊在很多的App設(shè)計中都有,如下圖所示:

該例子是我沒事的時候?qū)懙囊粋€小項目,具體源碼地址請訪問https://github.com/AlexSmille/YingMi。
該畫廊類似封面的效果,滑到中間的圖片會慢慢變大,離開的View會慢慢的縮小,同時可設(shè)置滑動監(jiān)聽和點擊監(jiān)聽。
網(wǎng)上有很多例子都是通過Gallery實現(xiàn)的,而上例的實現(xiàn)是通過ViewPager實現(xiàn),解決了性能優(yōu)化的問題,今天特此把它抽出來,封裝一下,以便以后的方便使用。最終實現(xiàn)的效果如下:

使用方式
布局中添加該自定義控件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 布局中添加自定義控件--> <com.mahao.alex.customviewdemo.viewpager.CoverFlowViewPager android:id="@+id/cover" android:layout_width="match_parent" android:layout_height="wrap_content" /> </RelativeLayout>
代碼中設(shè)置
代碼中設(shè)置分為以下幾個步驟:
•查找控件
•初始化數(shù)據(jù)
•將需要顯示的數(shù)據(jù)設(shè)置到控件上
•設(shè)置滑動監(jiān)聽
public class MainActivity extends AppCompatActivity {
private CoverFlowViewPager mCover;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mCover = (CoverFlowViewPager) findViewById(R.id.cover);
// 初始化數(shù)據(jù)
List<View> list = new ArrayList<>();
for(int i = 0;i<10;i++){
ImageView img = new ImageView(this);
img.setBackgroundColor(Color.parseColor("#"+getRandColorCode()));
list.add(img);
}
//設(shè)置顯示的數(shù)據(jù)
mCover.setViewList(list);
// 設(shè)置滑動的監(jiān)聽,該監(jiān)聽為當(dāng)前頁面滑動到中央時的索引
mCover.setOnPageSelectListener(new OnPageSelectListener() {
@Override
public void select(int position) {
Toast.makeText(getApplicationContext(),position+"",Toast.LENGTH_SHORT).show();
}
});
}
/**
* 獲取隨機顏色,便于區(qū)分
* @return
*/
public static String getRandColorCode(){
String r,g,b;
Random random = new Random();
r = Integer.toHexString(random.nextInt(256)).toUpperCase();
g = Integer.toHexString(random.nextInt(256)).toUpperCase();
b = Integer.toHexString(random.nextInt(256)).toUpperCase();
r = r.length()==1 ? "0" + r : r ;
g = g.length()==1 ? "0" + g : g ;
b = b.length()==1 ? "0" + b : b ;
return r+g+b;
}
}
實現(xiàn)原理
實現(xiàn)過程中有兩個難點:
•如何實現(xiàn)滑動過程中的放大與縮小
•如何顯示ViewPager中未被顯示的頁面
如何實現(xiàn)滑動過程中的放大與縮?。?/strong>
在設(shè)置每一個ViewPager 的頁面時,對每一個頁面都設(shè)置一個固定的padding值,這樣每個頁面都會顯示縮小狀態(tài)。同時ViewPager設(shè)置addOnPageChangeListener(),滑動監(jiān)聽,在該滑動監(jiān)聽中會回調(diào)ViewPager的滑動的狀態(tài),滑動的偏移量等,根據(jù)滑動的偏移量進行放大縮小。及根據(jù)padding值設(shè)置控件的顯示大小
如何顯示ViewPager中未被顯示的頁面
在xml中有一個不常用的屬性android:clipChildren,是否限制子View的顯示。設(shè)置為false,則子View的顯示不受父控件的限制。
代碼實現(xiàn)
編寫控件的布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:clipChildren="false"> <android.support.v4.view.ViewPager android:id="@+id/vp_conver_flow" android:layout_width="180dp" android:layout_height="260dp" android:layout_centerHorizontal="true" android:clipChildren="false" /> </RelativeLayout>
一個相對布局中嵌入一個ViewPager,相對布局用于確定顯示的范圍,ViewPager用以實現(xiàn)滑動,放大縮小等。
創(chuàng)建CoverFlowViewPager,加載布局
/**
*
* 實現(xiàn)封面瀏覽
* Created by alex_mahao on 2016/8/25.
*/
public class CoverFlowViewPager extends RelativeLayout implements OnPageSelectListener {
/**
* 用于左右滾動
*/
private ViewPager mViewPager;
public CoverFlowViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
inflate(context, R.layout.widget_cover_flow,this);
mViewPager = (ViewPager) findViewById(R.id.vp_conver_flow);
//init();
}
查找控件,并加載布局。
編寫適配器,實現(xiàn)滑動的監(jiān)聽
既然有了ViewPager,那么肯定要有適配器Adapter。因為我們要在滑動監(jiān)聽中,根據(jù)偏移量操作每一個子元素,放大或縮小,而對于子元素,當(dāng)然適配器最容易獲取,所以將Adapter實現(xiàn)了ViewPager的滑動監(jiān)聽接口。
/**
* 滾動的適配器
* Created by alex_mahao on 2016/8/25.
*/
public class CoverFlowAdapter extends PagerAdapter implements ViewPager.OnPageChangeListener {
/**
* 默認縮小的padding值
*/
public static int sWidthPadding;
public static int sHeightPadding;
/**
* 子元素的集合
*/
private List<View> mViewList;
/**
* 滑動監(jiān)聽的回調(diào)接口
*/
private OnPageSelectListener listener;
/**
* 上下文對象
*/
private Context mContext;
public CoverFlowAdapter(List<View> mImageViewList, Context context) {
this.mViewList = mImageViewList;
mContext = context;
// 設(shè)置padding值
sWidthPadding = dp2px(24);
sHeightPadding = dp2px(32);
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView(mViewList.get(position));
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
View view = mViewList.get(position);
container.addView(view);
return view;
}
@Override
public int getCount() {
return mViewList == null ? 0 : mViewList.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// 該方法回調(diào)ViewPager 的滑動偏移量
if (mViewList.size() > 0 && position < mViewList.size()) {
//當(dāng)前手指觸摸滑動的頁面,從0頁滑動到1頁 offset越來越大,padding越來越大
Log.i("info", "重新設(shè)置padding");
int outHeightPadding = (int) (positionOffset * sHeightPadding);
int outWidthPadding = (int) (positionOffset * sWidthPadding);
// 從0滑動到一時,此時position = 0,其應(yīng)該是縮小的,符合
mViewList.get(position).setPadding(outWidthPadding, outHeightPadding, outWidthPadding, outHeightPadding);
// position+1 為即將顯示的頁面,越來越大
if (position < mViewList.size() - 1) {
int inWidthPadding = (int) ((1 - positionOffset) * sWidthPadding);
int inHeightPadding = (int) ((1 - positionOffset) * sHeightPadding);
mViewList.get(position + 1).setPadding(inWidthPadding, inHeightPadding, inWidthPadding, inHeightPadding);
}
}
}
@Override
public void onPageSelected(int position) {
// 回調(diào)選擇的接口
if (listener != null) {
listener.select(position);
}
}
@Override
public void onPageScrollStateChanged(int state) {
}
/**
* 當(dāng)將某一個作為最中央時的回調(diào)
*
* @param listener
*/
public void setOnPageSelectListener(OnPageSelectListener listener) {
this.listener = listener;
}
/**
* dp 轉(zhuǎn) px
*
* @param dp
* @return
*/
public int dp2px(int dp) {
int px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mContext.getResources().getDisplayMetrics());
return px;
}
}
改代碼分為兩部分,PagerAdapter實現(xiàn),滑動監(jiān)聽的實現(xiàn)。PagerAdapter的實現(xiàn)不在多說,最基礎(chǔ)的東西。重點在滑動監(jiān)聽的實現(xiàn)。
滑動監(jiān)聽有三個回調(diào)方法:其中onPageScrolled(int position, float positionOffset, int positionOffsetPixels)回調(diào)的便是ViewPager的滑動得偏移量,我們再次動態(tài)的設(shè)置相應(yīng)元素的padding值,實現(xiàn)放大縮小。
onPageSelected()為選中的回調(diào),通過自定義接口的方式回調(diào)給其調(diào)用者。后面會提。
初始化ViewPager
既然有了適配器,那么自然就開始編寫適配器的部分:
/**
* 初始化方法
*/
private void init() {
// 構(gòu)造適配器,傳入數(shù)據(jù)源
mAdapter = new CoverFlowAdapter(mViewList,getContext());
// 設(shè)置選中的回調(diào)
mAdapter.setOnPageSelectListener(this);
// 設(shè)置適配器
mViewPager.setAdapter(mAdapter);
// 設(shè)置滑動的監(jiān)聽,因為adpter實現(xiàn)了滑動回調(diào)的接口,所以這里直接設(shè)置adpter
mViewPager.addOnPageChangeListener(mAdapter);
// 自己百度
mViewPager.setOffscreenPageLimit(5);
// 設(shè)置觸摸事件的分發(fā)
setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// 傳遞給ViewPager 進行滑動處理
return mViewPager.dispatchTouchEvent(event);
}
});
}
注釋很詳細,唯一需要解釋的便是事件的分發(fā)。
我們的ViewPager的大小是固定的,只有中間的顯示區(qū)域,那么對于手指在兩個側(cè)邊滑動時,ViewPager自然接受不到觸摸事件,通過設(shè)置外層相對布局的觸摸事件監(jiān)聽,將觸摸的事件傳遞到ViewPager,實現(xiàn)滑動ViewPager之外區(qū)域時,ViewPager仍能夠?qū)崿F(xiàn)對應(yīng)的滑動。
數(shù)據(jù)源的包裝
適配器有了,ViewPager也有了,那么只剩下數(shù)據(jù)源了。
因為我們是根據(jù)設(shè)置padding值實現(xiàn)的,那么對于需要顯示的控件,他的背景將無法實現(xiàn)放大縮小,所以對控件在包裝一層外部控件,這樣設(shè)置外部控件的padding值,自然需要顯示的控件會放大縮小。
/**
* 設(shè)置顯示的數(shù)據(jù),進行一層封裝
* @param lists
*/
public void setViewList(List<View> lists){
if(lists==null){
return;
}
mViewList.clear();
for(View view:lists){
FrameLayout layout = new FrameLayout(getContext());
// 設(shè)置padding 值,默認縮小
layout.setPadding(CoverFlowAdapter.sWidthPadding,CoverFlowAdapter.sHeightPadding,CoverFlowAdapter.sWidthPadding,CoverFlowAdapter.sHeightPadding);
layout.addView(view);
mViewList.add(layout);
}
// 刷新數(shù)據(jù)
mAdapter.notifyDataSetChanged();
}
選中監(jiān)聽的回調(diào)
當(dāng)我們滑動時,可能會根據(jù)不同的滑動,顯示不同的數(shù)據(jù)。
通過設(shè)置滑動監(jiān)聽之后,對onPageSelected實現(xiàn)層層的接口回調(diào)。
接口的定義OnPageSelectListener
public interface OnPageSelectListener {
void select(int position);
}
CoverFlowAdapter中添加回調(diào)
@Override
public void onPageSelected(int position) {
// 回調(diào)選擇的接口
if (listener != null) {
listener.select(position);
}
}
CoverFlowViewPager中添加回調(diào)
// 顯示的回調(diào)
@Override
public void select(int position) {
if(listener!=null){
listener.select(position);
}
}
點擊事件的設(shè)置
直接對數(shù)據(jù)源循環(huán)設(shè)置監(jiān)聽即可
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android HorizontalScrollView內(nèi)子控件橫向拖拽實例代碼
本文主要介紹Android HorizontalScrollView的使用,這里給大家一個實例來展示HorizontalScrollView內(nèi)子控件橫向拖拽的效果實現(xiàn),有需要的小伙伴可以參考下2016-07-07
Android系統(tǒng)進程間通信(IPC)機制Binder中的Client獲得Server遠程接口過程源代碼分析
本文主要介紹Android 通信Binder中的Client獲得Server遠程接口,這里對Android Binder 中Client中Server 源碼做了詳細分析介紹,有研究Android源碼的小伙伴可以參考下2016-08-08

