Android利用ViewPager實(shí)現(xiàn)可滑動(dòng)放大縮小畫(huà)廊效果
畫(huà)廊在很多的App設(shè)計(jì)中都有,如下圖所示:
該例子是我沒(méi)事的時(shí)候?qū)懙囊粋€(gè)小項(xiàng)目,具體源碼地址請(qǐng)?jiān)L問(wèn)https://github.com/AlexSmille/YingMi。
該畫(huà)廊類似封面的效果,滑到中間的圖片會(huì)慢慢變大,離開(kāi)的View會(huì)慢慢的縮小,同時(shí)可設(shè)置滑動(dòng)監(jiān)聽(tīng)和點(diǎn)擊監(jiān)聽(tīng)。
網(wǎng)上有很多例子都是通過(guò)Gallery實(shí)現(xiàn)的,而上例的實(shí)現(xiàn)是通過(guò)ViewPager實(shí)現(xiàn),解決了性能優(yōu)化的問(wèn)題,今天特此把它抽出來(lái),封裝一下,以便以后的方便使用。最終實(shí)現(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è)置分為以下幾個(gè)步驟:
•查找控件
•初始化數(shù)據(jù)
•將需要顯示的數(shù)據(jù)設(shè)置到控件上
•設(shè)置滑動(dòng)監(jiān)聽(tīng)
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è)置滑動(dòng)的監(jiān)聽(tīng),該監(jiān)聽(tīng)為當(dāng)前頁(yè)面滑動(dòng)到中央時(shí)的索引 mCover.setOnPageSelectListener(new OnPageSelectListener() { @Override public void select(int position) { Toast.makeText(getApplicationContext(),position+"",Toast.LENGTH_SHORT).show(); } }); } /** * 獲取隨機(jī)顏色,便于區(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; } }
實(shí)現(xiàn)原理
實(shí)現(xiàn)過(guò)程中有兩個(gè)難點(diǎn):
•如何實(shí)現(xiàn)滑動(dòng)過(guò)程中的放大與縮小
•如何顯示ViewPager中未被顯示的頁(yè)面
如何實(shí)現(xiàn)滑動(dòng)過(guò)程中的放大與縮小?
在設(shè)置每一個(gè)ViewPager 的頁(yè)面時(shí),對(duì)每一個(gè)頁(yè)面都設(shè)置一個(gè)固定的padding值,這樣每個(gè)頁(yè)面都會(huì)顯示縮小狀態(tài)。同時(shí)ViewPager設(shè)置addOnPageChangeListener(),滑動(dòng)監(jiān)聽(tīng),在該滑動(dòng)監(jiān)聽(tīng)中會(huì)回調(diào)ViewPager的滑動(dòng)的狀態(tài),滑動(dòng)的偏移量等,根據(jù)滑動(dòng)的偏移量進(jìn)行放大縮小。及根據(jù)padding值設(shè)置控件的顯示大小
如何顯示ViewPager中未被顯示的頁(yè)面
在xml中有一個(gè)不常用的屬性android:clipChildren,是否限制子View的顯示。設(shè)置為false,則子View的顯示不受父控件的限制。
代碼實(shí)現(xiàn)
編寫(xiě)控件的布局文件
<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>
一個(gè)相對(duì)布局中嵌入一個(gè)ViewPager,相對(duì)布局用于確定顯示的范圍,ViewPager用以實(shí)現(xiàn)滑動(dòng),放大縮小等。
創(chuàng)建CoverFlowViewPager,加載布局
/** * * 實(shí)現(xiàn)封面瀏覽 * Created by alex_mahao on 2016/8/25. */ public class CoverFlowViewPager extends RelativeLayout implements OnPageSelectListener { /** * 用于左右滾動(dòng) */ 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ě)適配器,實(shí)現(xiàn)滑動(dòng)的監(jiān)聽(tīng)
既然有了ViewPager,那么肯定要有適配器Adapter。因?yàn)槲覀円诨瑒?dòng)監(jiān)聽(tīng)中,根據(jù)偏移量操作每一個(gè)子元素,放大或縮小,而對(duì)于子元素,當(dāng)然適配器最容易獲取,所以將Adapter實(shí)現(xiàn)了ViewPager的滑動(dòng)監(jiān)聽(tīng)接口。
/** * 滾動(dòng)的適配器 * Created by alex_mahao on 2016/8/25. */ public class CoverFlowAdapter extends PagerAdapter implements ViewPager.OnPageChangeListener { /** * 默認(rèn)縮小的padding值 */ public static int sWidthPadding; public static int sHeightPadding; /** * 子元素的集合 */ private List<View> mViewList; /** * 滑動(dòng)監(jiān)聽(tīng)的回調(diào)接口 */ private OnPageSelectListener listener; /** * 上下文對(duì)象 */ 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 的滑動(dòng)偏移量 if (mViewList.size() > 0 && position < mViewList.size()) { //當(dāng)前手指觸摸滑動(dòng)的頁(yè)面,從0頁(yè)滑動(dòng)到1頁(yè) offset越來(lái)越大,padding越來(lái)越大 Log.i("info", "重新設(shè)置padding"); int outHeightPadding = (int) (positionOffset * sHeightPadding); int outWidthPadding = (int) (positionOffset * sWidthPadding); // 從0滑動(dòng)到一時(shí),此時(shí)position = 0,其應(yīng)該是縮小的,符合 mViewList.get(position).setPadding(outWidthPadding, outHeightPadding, outWidthPadding, outHeightPadding); // position+1 為即將顯示的頁(yè)面,越來(lái)越大 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)將某一個(gè)作為最中央時(shí)的回調(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實(shí)現(xiàn),滑動(dòng)監(jiān)聽(tīng)的實(shí)現(xiàn)。PagerAdapter的實(shí)現(xiàn)不在多說(shuō),最基礎(chǔ)的東西。重點(diǎn)在滑動(dòng)監(jiān)聽(tīng)的實(shí)現(xiàn)。
滑動(dòng)監(jiān)聽(tīng)有三個(gè)回調(diào)方法:其中onPageScrolled(int position, float positionOffset, int positionOffsetPixels)回調(diào)的便是ViewPager的滑動(dòng)得偏移量,我們?cè)俅蝿?dòng)態(tài)的設(shè)置相應(yīng)元素的padding值,實(shí)現(xiàn)放大縮小。
onPageSelected()為選中的回調(diào),通過(guò)自定義接口的方式回調(diào)給其調(diào)用者。后面會(huì)提。
初始化ViewPager
既然有了適配器,那么自然就開(kāi)始編寫(xiě)適配器的部分:
/** * 初始化方法 */ private void init() { // 構(gòu)造適配器,傳入數(shù)據(jù)源 mAdapter = new CoverFlowAdapter(mViewList,getContext()); // 設(shè)置選中的回調(diào) mAdapter.setOnPageSelectListener(this); // 設(shè)置適配器 mViewPager.setAdapter(mAdapter); // 設(shè)置滑動(dòng)的監(jiān)聽(tīng),因?yàn)閍dpter實(shí)現(xiàn)了滑動(dòng)回調(diào)的接口,所以這里直接設(shè)置adpter mViewPager.addOnPageChangeListener(mAdapter); // 自己百度 mViewPager.setOffscreenPageLimit(5); // 設(shè)置觸摸事件的分發(fā) setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // 傳遞給ViewPager 進(jìn)行滑動(dòng)處理 return mViewPager.dispatchTouchEvent(event); } }); }
注釋很詳細(xì),唯一需要解釋的便是事件的分發(fā)。
我們的ViewPager的大小是固定的,只有中間的顯示區(qū)域,那么對(duì)于手指在兩個(gè)側(cè)邊滑動(dòng)時(shí),ViewPager自然接受不到觸摸事件,通過(guò)設(shè)置外層相對(duì)布局的觸摸事件監(jiān)聽(tīng),將觸摸的事件傳遞到ViewPager,實(shí)現(xiàn)滑動(dòng)ViewPager之外區(qū)域時(shí),ViewPager仍能夠?qū)崿F(xiàn)對(duì)應(yīng)的滑動(dòng)。
數(shù)據(jù)源的包裝
適配器有了,ViewPager也有了,那么只剩下數(shù)據(jù)源了。
因?yàn)槲覀兪歉鶕?jù)設(shè)置padding值實(shí)現(xiàn)的,那么對(duì)于需要顯示的控件,他的背景將無(wú)法實(shí)現(xiàn)放大縮小,所以對(duì)控件在包裝一層外部控件,這樣設(shè)置外部控件的padding值,自然需要顯示的控件會(huì)放大縮小。
/** * 設(shè)置顯示的數(shù)據(jù),進(jìn)行一層封裝 * @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 值,默認(rèn)縮小 layout.setPadding(CoverFlowAdapter.sWidthPadding,CoverFlowAdapter.sHeightPadding,CoverFlowAdapter.sWidthPadding,CoverFlowAdapter.sHeightPadding); layout.addView(view); mViewList.add(layout); } // 刷新數(shù)據(jù) mAdapter.notifyDataSetChanged(); }
選中監(jiān)聽(tīng)的回調(diào)
當(dāng)我們滑動(dòng)時(shí),可能會(huì)根據(jù)不同的滑動(dòng),顯示不同的數(shù)據(jù)。
通過(guò)設(shè)置滑動(dòng)監(jiān)聽(tīng)之后,對(duì)onPageSelected實(shí)現(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); } }
點(diǎn)擊事件的設(shè)置
直接對(duì)數(shù)據(jù)源循環(huán)設(shè)置監(jiān)聽(tīng)即可
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android實(shí)現(xiàn)漂亮的Gallery畫(huà)廊
- Android開(kāi)發(fā)中畫(huà)廊視圖Gallery的兩種使用方法分析
- Android高級(jí)組件Gallery畫(huà)廊視圖使用方法詳解
- Android開(kāi)發(fā)實(shí)現(xiàn)Gallery畫(huà)廊效果的方法
- Android viewpager 3D畫(huà)廊的實(shí)現(xiàn)方法
- Android ViewPager畫(huà)廊效果詳解及實(shí)例
- Android畫(huà)廊效果之ViewPager顯示多個(gè)圖片
- Android App開(kāi)發(fā)中使用RecyclerView實(shí)現(xiàn)Gallery畫(huà)廊的實(shí)例
- Android使用viewpager實(shí)現(xiàn)畫(huà)廊式效果
相關(guān)文章
Android來(lái)電攔截的實(shí)現(xiàn)方法
這篇文章主要為大家詳細(xì)介紹了Android來(lái)電攔截的實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10Android HorizontalScrollView內(nèi)子控件橫向拖拽實(shí)例代碼
本文主要介紹Android HorizontalScrollView的使用,這里給大家一個(gè)實(shí)例來(lái)展示HorizontalScrollView內(nèi)子控件橫向拖拽的效果實(shí)現(xiàn),有需要的小伙伴可以參考下2016-07-07Android系統(tǒng)進(jìn)程間通信(IPC)機(jī)制Binder中的Client獲得Server遠(yuǎn)程接口過(guò)程源代碼分析
本文主要介紹Android 通信Binder中的Client獲得Server遠(yuǎn)程接口,這里對(duì)Android Binder 中Client中Server 源碼做了詳細(xì)分析介紹,有研究Android源碼的小伙伴可以參考下2016-08-08關(guān)于Android多渠道打包問(wèn)題看這一篇就夠了
這篇文章主要介紹了關(guān)于Android程序的多渠道打包方法,還不會(huì)的同學(xué)快進(jìn)來(lái)學(xué)習(xí)下吧,建議收藏以防迷路2021-08-08