Android-通過自定義ViewPager(中間放大效果)
/**稀土掘金,時光不老**/
大家好,很早就想寫部落格了,一是工作忙,二是缺乏原創性,三當然是自己的能力不夠啦,寫這篇部落格是很惶恐。。。。請多多包涵
/****************************
-------- ---------
-------
***************************************/
好了,回到正題上來,最近看到韓海龍的部落格 http://hanhailong.com/
看了裡面的效果不錯,就試著模仿了一下,加了一些自己的東西,好啦,廢話不說了,先看效果圖如下:
一. 首先說說核心點吧:
1. 在包裹ViewPager的父佈局 和 ViewPager中 的android:clipChildren設定為false,意味著不限制子View在 其範圍內,也就是說子view可以超 出父view的範圍
2. 左右滑動的縮放通過ViewPager的PageTransformer來實現縮放動畫
3. 螢幕的點選攔截事件分發給ViewPager
4. 自定義Scroll類 ,用於調節ViewPager左右滑動速度
OK ,核心講完了,進入我們程式碼,佈局模組,非常簡單----------------------|
|
|
看這裡,定義佈局模組--主佈局-activity_gallery_view_pager
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:tools="http://schemas.android.com/tools" android:fitsSystemWindows="true" android:orientation="vertical" tools:context=".GalleryViewPagerActivity"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay"> </android.support.v7.widget.Toolbar> </android.support.design.widget.AppBarLayout> <include layout="@layout/content_main"/> </LinearLayout>
|
|
|
看這裡,定義佈局模組--VeiwPager佈局-content_main
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/page_container" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white" android:clipChildren="false" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <!--RelativeLayout和自定義的ClipViewPager都各自添加了一個屬性android:clipChildren=”false”, clipChildren的意思是是否限制子View在其範圍內,這個預設是true, 也就是預設是限制子view在其範圍的--> <view.ClipViewPager android:id="@+id/viewpager" android:layout_width="200dp" android:layout_height="200dp" android:layout_centerInParent="true" android:clipChildren="false" android:overScrollMode="never" /> </RelativeLayout>
上面的RelativeLayout和自定義的ClipViewPager都各自添加了一個屬性android:clipChildren=”false”,clipChildren的意思是是否限制子View在其範圍內,這個預設是true,也就是預設是限制子view在其範圍的.
給ViewPager設定滑動速度通過自定義Scroller實現,關於Scroller有興趣的童鞋可以去看看
mViewPager = (ClipViewPager) findViewById(R.id.viewpager); /**調節ViewPager的滑動速度**/ mViewPager.setSpeedScroller(300);給ViewPager設定縮放動畫,這裡通過PageTransformer來實現
/**給ViewPager設定縮放動畫,這裡通過PageTransformer來實現**/ mViewPager.setPageTransformer(true, new ScalePageTransformer());再來看ScalePageTransformer的實現,核心就是實現transformPage(View page, float position)這個方法
這段程式碼檢視韓海龍大神的(站在前輩的基礎上學習)
/** * Created by HanHailong on 15/9/27. * * description: 其實核心程式碼就是這個動畫實現部分,這裡設定了一個最大縮放和最小縮放比例, * 當處於最中間的view往左邊滑動時,它的position值是小於0的, * 並且是越來越小,它右邊的view的position是從1逐漸減小到0的 */ public class ScalePageTransformer implements ViewPager.PageTransformer { public static final float MAX_SCALE = 1.2f; public static final float MIN_SCALE = 0.6f; /**核心就是實現transformPage(View page, float position)這個方法**/ @Override public void transformPage(View page, float position) { if (position < -1) { position = -1; } else if (position > 1) { position = 1; } float tempScale = position < 0 ? 1 + position : 1 - position; float slope = (MAX_SCALE - MIN_SCALE) / 1; //一個公式 float scaleValue = MIN_SCALE + tempScale * slope; page.setScaleX(scaleValue); page.setScaleY(scaleValue); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { page.getParent().requestLayout(); } } }
呼 呼 ...看看自己沒啥遺漏了。美女帥哥們別急-------下面我們看下在Activity的完整實現程式碼(必要的註釋已經新增 )
import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.MotionEvent; import android.view.View; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import adapter.TubatuAdapter; import utils.ScalePageTransformer; import view.ClipViewPager; /** * Created by wujian on 2016/3/23. * description: 畫廊式中間放大效果 */ public class GalleryViewPagerActivity extends AppCompatActivity { private final static float TARGET_HEAP_UTILIZATION = 0.75f; private TubatuAdapter mPagerAdapter; private ClipViewPager mViewPager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_gallery_view_pager); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); initFindView(); initData(); } private void initFindView() { mViewPager = (ClipViewPager) findViewById(R.id.viewpager); /**調節ViewPager的滑動速度**/ mViewPager.setSpeedScroller(300); /**給ViewPager設定縮放動畫,這裡通過PageTransformer來實現**/ mViewPager.setPageTransformer(true, new ScalePageTransformer()); List<String> strList = Arrays.asList("現代", "簡約", "歐式", "中式", "美式", "地中海", "東南亞", "日式"); /** * 需要將整個頁面的事件分發給ViewPager,不然的話只有ViewPager中間的view能滑動,其他的都不能滑動, * 這是肯定的,因為ViewPager總體佈局就是中間那一塊大小,其他的子佈局都跑到ViewPager外面來了 */ findViewById(R.id.page_container).setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return mViewPager.dispatchTouchEvent(event); } }); mPagerAdapter = new TubatuAdapter(this,strList); mViewPager.setAdapter(mPagerAdapter); } private void initData() { List<Integer> list = new ArrayList<>(); list.add(R.mipmap.style_xiandai); list.add(R.mipmap.style_jianyue); list.add(R.mipmap.style_oushi); list.add(R.mipmap.style_zhongshi); list.add(R.mipmap.style_meishi); list.add(R.mipmap.style_dzh); list.add(R.mipmap.style_dny); list.add(R.mipmap.style_rishi); /**這裡需要將setOffscreenPageLimit的值設定成資料來源的總個數,如果不加這句話,會導致左右切換異常;**/ mViewPager.setOffscreenPageLimit(list.size()); mPagerAdapter.addAll(list); } }
來來,瞧瞧上面Activity程式碼的解釋
一是setOffscreenPageLimit的值設定成資料來源的總個數,如果不加這句話,會導致左右切換異常;
二是需要將整個頁面的事件分發給ViewPager,不然的話只有ViewPager中間的view能滑動,其他的都不能滑動,這是肯定 的,因為ViewPager總體佈局就是中間那一塊大小,其他的子佈局都跑到ViewPager外面來了;
三是你發現ViewPager加了setOnTouchListener方法後,滑動是可以了,但是點選左右兩邊不能切換,這裡需要重寫 ViewPager的dispatchTouchEvent方法;
四是ViewPager設定了setSpeedScroller(300)設定滑動時間為300毫秒;
下面看ClipViewPager程式碼
import android.content.Context; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.animation.AccelerateInterpolator; import java.lang.reflect.Field; import utils.SpeedScroller; /** * Created by wujian 15/9/27. */ public class ClipViewPager extends ViewPager { public ClipViewPager(Context context) { super(context); } public ClipViewPager(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_UP) { View view = viewOfClickOnScreen(ev); if (view != null) { int index = indexOfChild(view); if (getCurrentItem() != index) { setCurrentItem(indexOfChild(view)); } } } return super.dispatchTouchEvent(ev); } /** * @param ev * @return */ private View viewOfClickOnScreen(MotionEvent ev) { int childCount = getChildCount(); int[] location = new int[2]; for (int i = 0; i < childCount; i++) { View v = getChildAt(i); v.getLocationOnScreen(location); int minX = location[0]; int minY = getTop(); int maxX = location[0] + v.getWidth(); int maxY = getBottom(); float x = ev.getX(); float y = ev.getY(); if ((x > minX && x < maxX) && (y > minY && y < maxY)) { return v; } } return null; } /**利用java反射機制,將自定義Scroll和ViewPager結合來調節ViewPager的滑動效果**/ public void setSpeedScroller(int duration) { try { Field mScroller = null; mScroller = ViewPager.class.getDeclaredField("mScroller"); mScroller.setAccessible(true); SpeedScroller scroller = new SpeedScroller(this.getContext(), new AccelerateInterpolator()); mScroller.set(this, scroller); scroller.setmDuration(duration); }catch(NoSuchFieldException e){ }catch (IllegalArgumentException e){ }catch (IllegalAccessException e){ } } }
|
|
……...............看這裡 O(∩_∩)O
實現原理就是手指點選螢幕,如果點選的位置恰好落在ViewPager某個子View範圍內,就讓ViewPager切換到哪個子View!viewOfClickOnScreen方法是獲取手指點選ViewPager中的哪個子View,最後呼叫setCurrentItem切換到相應的子View,其中設定ViewPager的滑動速度通過Scroller實現
/**利用java反射機制,將自定義Scroll和ViewPager結合來調節ViewPager的滑動效果**/下面看SpeedScroller程式碼
importandroid.content.Context; import android.view.animation.Interpolator; import android.widget.Scroller; /** * Created by wujian on 2016/3/23. * description: 自定義Scroll類,用於調節滑動速度 */ public class SpeedScroller extends Scroller { private int mDuration = 1500; public SpeedScroller(Context context) { super(context); } public SpeedScroller(Context context, Interpolator interpolator) { super(context, interpolator); } @Override public void startScroll(int startX, int startY, int dx, int dy, int duration) { // Ignore received duration, use fixed one instead super.startScroll(startX, startY, dx, dy, mDuration); } @Override public void startScroll(int startX, int startY, int dx, int dy) { // Ignore received duration, use fixed one instead super.startScroll(startX, startY, dx, dy, mDuration); } public void setmDuration(int time) { mDuration = time; } public int getmDuration() { return mDuration; } }
霍霍 差點忘了還有程式碼 TubatuAdapter------- (*^__^*) 嘻嘻……
import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.Toast; import java.util.ArrayList; import java.util.List; /** * Created by wujian on 2016/3/23. * RecyclingPagerAdapter是Jake WhartonAndroid大神封裝的可用於複用的PagerAdapter。 */ public class TubatuAdapter extends RecyclingPagerAdapter { private List<Integer> mList; private Context mContext; private List<String> strList; public TubatuAdapter(Context context,List<String> strList) { mList = new ArrayList<>(); mContext = context; this.strList = strList; } public void addAll(List<Integer> list) { mList.addAll(list); notifyDataSetChanged(); } @Override public View getView(final int position, View convertView, ViewGroup container) { ImageView imageView = null; if (convertView == null) { imageView = new ImageView(mContext); } else { imageView = (ImageView) convertView; } imageView.setTag(position); imageView.setImageResource(mList.get(position)); imageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(mContext, strList.get(position), Toast.LENGTH_SHORT).show(); } }); return imageView; } @Override public int getCount() { return mList.size(); } }
補充一下,RecyclingPagerAdapter是Jake
WhartonAndroid大神封裝的可用於複用的PagerAdapter。如下,請看---------------------------
import android.support.v4.view.PagerAdapter; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; /** * A {@link PagerAdapter} which behaves like an {@link android.widget.Adapter} with view types and * view recycling. */ public abstract class RecyclingPagerAdapter extends PagerAdapter { static final int IGNORE_ITEM_VIEW_TYPE = AdapterView.ITEM_VIEW_TYPE_IGNORE; private final RecycleBin recycleBin; public RecyclingPagerAdapter() { this(new RecycleBin()); } RecyclingPagerAdapter(RecycleBin recycleBin) { this.recycleBin = recycleBin; recycleBin.setViewTypeCount(getViewTypeCount()); } @Override public void notifyDataSetChanged() { recycleBin.scrapActiveViews(); super.notifyDataSetChanged(); } @Override public final Object instantiateItem(ViewGroup container, int position) { int viewType = getItemViewType(position); View view = null; if (viewType != IGNORE_ITEM_VIEW_TYPE) { view = recycleBin.getScrapView(position, viewType); } view = getView(position, view, container); container.addView(view); return view; } @Override public final void destroyItem(ViewGroup container, int position, Object object) { View view = (View) object; container.removeView(view); int viewType = getItemViewType(position); if (viewType != IGNORE_ITEM_VIEW_TYPE) { recycleBin.addScrapView(view, position, viewType); } } @Override public final boolean isViewFromObject(View view, Object object) { return view == object; } /** * <p> * Returns the number of types of Views that will be created by * {@link #getView}. Each type represents a set of views that can be * converted in {@link #getView}. If the adapter always returns the same * type of View for all items, this method should return 1. * </p> * <p> * This method will only be called when when the adapter is set on the * the {@link AdapterView}. * </p> * * @return The number of types of Views that will be created by this adapter */ public int getViewTypeCount() { return 1; } /** * Get the type of View that will be created by {@link #getView} for the specified item. * * @param position The position of the item within the adapter's data set whose view type we * want. * @return An integer representing the type of View. Two views should share the same type if one * can be converted to the other in {@link #getView}. Note: Integers must be in the * range 0 to {@link #getViewTypeCount} - 1. {@link #IGNORE_ITEM_VIEW_TYPE} can * also be returned. * @see #IGNORE_ITEM_VIEW_TYPE */ @SuppressWarnings("UnusedParameters") // Argument potentially used by subclasses. public int getItemViewType(int position) { return 0; } /** * Get a View that displays the data at the specified position in the data set. You can either * create a View manually or inflate it from an XML layout file. When the View is inflated, the * parent View (GridView, ListView...) will apply default layout parameters unless you use * {@link android.view.LayoutInflater#inflate(int, ViewGroup, boolean)} * to specify a root view and to prevent attachment to the root. * * @param position The position of the item within the adapter's data set of the item whose view * we want. * @param convertView The old view to reuse, if possible. Note: You should check that this view * is non-null and of an appropriate type before using. If it is not possible to convert * this view to display the correct data, this method can create a new view. * Heterogeneous lists can specify their number of view types, so that this View is * always of the right type (see {@link #getViewTypeCount()} and * {@link #getItemViewType(int)}). * @param container The parent that this view will eventually be attached to * @return A View corresponding to the data at the specified position. */ public abstract View getView(int position, View convertView, ViewGroup container); }再來一個RecycleBin類
import android.os.Build; import android.util.SparseArray; import android.view.View; /** * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the * start of a layout. By construction, they are displaying current information. At the end of * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that * could potentially be used by the adapter to avoid allocating views unnecessarily. * <p/> * This class was taken from Android's implementation of {@link android.widget.AbsListView} which * is copyrighted 2006 The Android Open Source Project. */ public class RecycleBin { /** * Views that were on screen at the start of layout. This array is populated at the start of * layout, and at the end of layout all view in activeViews are moved to scrapViews. * Views in activeViews represent a contiguous range of Views, with position of the first * view store in mFirstActivePosition. */ private View[] activeViews = new View[0]; private int[] activeViewTypes = new int[0