1.setAdapter()方法
// 我们的FragmentPagerAdapter、FragmentStatePagerAdapter就是继承PagerAdapter的。
mViewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
@NonNull
@Override
public Fragment getItem(int position) {
return ItemFragment.newInstance(items[position]);
}
@Override
public int getCount() {
return items.length;
}
});
看看其setAdapter的源码:主要是清理前PagerAdapter,并为这个PagerAdapter做一些配置
public void setAdapter(PagerAdapter adapter) {
//1.如果已经设置过PagerAdapter,即mAdapter != null,
// 则做一些清理工作
if (mAdapter != null) {
//2.清除观察者
mAdapter.setViewPagerObserver(null);
//3.回调startUpdate函数,告诉PagerAdapter开始更新要显示的页面
mAdapter.startUpdate(this);
//4.如果之前保存有页面,则将之前所有的页面destroy掉
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
mAdapter.destroyItem(this, ii.position, ii.object);
}
//5.回调finishUpdate,告诉PagerAdapter结束更新
mAdapter.finishUpdate(this);
//6.将所有的页面清除
mItems.clear();
//7.将所有的非Decor View移除,即将页面移除
removeNonDecorViews();
//8.当前的显示页面重置到第一个
mCurItem = 0;
//9.滑动重置到(0,0)位置
scrollTo(0, 0);
}
//10.保存上一次的PagerAdapter
final PagerAdapter oldAdapter = mAdapter;
//11.设置mAdapter为新的PagerAdapter
mAdapter = adapter;
//12.设置期望的适配器中的页面数量为0个
mExpectedAdapterCount = 0;
//13.如果设置的PagerAdapter不为null
if (mAdapter != null) {
//14.确保观察者不为null,观察者主要是用于监视数据源的内容发生变化
if (mObserver == null) {
mObserver = new PagerObserver();
}
//15.将观察者设置到PagerAdapter中
mAdapter.setViewPagerObserver(mObserver);
mPopulatePending = false;
//16.保存上一次是否是第一次Layout
final boolean wasFirstLayout = mFirstLayout;
//17.设定当前为第一次Layout
mFirstLayout = true;
//18.更新期望的数据源中页面个数
mExpectedAdapterCount = mAdapter.getCount();
//19.如果有数据需要恢复
if (mRestoredCurItem >= 0) {
//20.回调PagerAdapter的restoreState函数
mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
setCurrentItemInternal(mRestoredCurItem, false, true);
//21.标记无需再恢复
mRestoredCurItem = -1;
mRestoredAdapterState = null;
mRestoredClassLoader = null;
} else if (!wasFirstLayout) {//如果在此之前不是第一次Layout
//22.由于ViewPager并不是将所有页面作为子View,
// 而是最多缓存用户指定缓存个数*2(左右两边,可能左边或右边没有那么多页面)
//因此需要创建和销毁页面,populate主要工作就是这些
populate();
} else {
//23.重新布局(Layout)
requestLayout();
}
}
//24.如果PagerAdapter发生变化,并且设置了OnAdapterChangeListener监听器
// 则回调OnAdapterChangeListener的onAdapterChanged函数
if (mAdapterChangeListener != null && oldAdapter != adapter) {
mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);
}
}
在setAdapter()中populate()方法来创建和销毁itemView,这个方法又会调用带一个需要定位显示页面的参数populate(int newCurrentItem)
1.2 populate(int newCurrentItem)方法
这个方法主要是创建和销毁,并缓存页面。
ViewPager里面有多少界面都不会卡,会不断的去销毁和创建页面,默认不光会创建当前页面,还会创建相邻的offscreenPageLimit页面,offscreenPageLimit代表相邻页面的个数,可以由用户通过setOffscreenPageLimit()方法指定,默认是1。而创建会调用PagerAdapter的instantiateItem()方法有一个过时了我们不要用过时的,而销毁会调用PagerAdapter的destroyItem()。

void populate(int newCurrentItem) {
ItemInfo oldCurInfo = null;
if (mCurItem != newCurrentItem) {
oldCurInfo = infoForPosition(mCurItem);
mCurItem = newCurrentItem;
}
if (mAdapter == null) {
//对子View的绘制顺序进行排序,优先绘制Decor View
//再按照position从小到大排序
sortChildDrawingOrder();
return;
}
//如果我们正在等待populate,那么在用户手指抬起切换到新的位置期间应该推迟创建子View,
// 直到滚动到最终位置再去创建,以免在这个期间出现差错
if (mPopulatePending) {
if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
//对子View的绘制顺序进行排序,优先绘制Decor View
//再按照position从小到大排序
sortChildDrawingOrder();
return;
}
//同样,在ViewPager没有attached到window之前,不要populate.
// 这是因为如果我们在恢复View的层次结构之前进行populate,可能会与要恢复的内容有冲突
if (getWindowToken() == null) {
return;
}
//回调PagerAdapter的startUpdate函数,
// 告诉PagerAdapter开始更新要显示的页面
mAdapter.startUpdate(this);
final int pageLimit = mOffscreenPageLimit;
//确保起始位置大于等于0,如果用户设置了缓存页面数量,第一个页面为当前页面减去缓存页面数量
final int startPos = Math.max(0, mCurItem - pageLimit);
//保存数据源中的数据个数
final int N = mAdapter.getCount();
//确保最后的位置小于等于数据源中数据个数-1,
// 如果用户设置了缓存页面数量,第一个页面为当前页面加缓存页面数量
final int endPos = Math.min(N - 1, mCurItem + pageLimit);
//判断用户是否增减了数据源的元素,如果增减了且没有调用notifyDataSetChanged,则抛出异常
if (N != mExpectedAdapterCount) {
//resName用于抛异常显示
String resName;
try {
resName = getResources().getResourceName(getId());
} catch (Resources.NotFoundException e) {
resName = Integer.toHexString(getId());
}
throw new IllegalStateException("The application's PagerAdapter changed the adapter's" +
" contents without calling PagerAdapter#notifyDataSetChanged!" +
" Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N +
" Pager id: " + resName +
" Pager class: " + getClass() +
" Problematic adapter: " + mAdapter.getClass());
}
//定位到当前获焦的页面,如果没有的话,则添加一个
int curIndex = -1;
ItemInfo curItem = null;
//遍历每个页面对应的ItemInfo,找出获焦页面
for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
final ItemInfo ii = mItems.get(curIndex);
//找到当前页面对应的ItemInfo后,跳出循环
if (ii.position >= mCurItem) {
if (ii.position == mCurItem) curItem = ii;
break;
}
}
//如果没有找到获焦的页面,说明mItems列表里面没有保存获焦页面,
// 需要将获焦页面加入到mItems里面
if (curItem == null && N > 0) {
curItem = addNewItem(mCurItem, curIndex);
}
//默认缓存当前页面的左右两边的页面,如果用户设定了缓存页面数量,
// 则将当前页面两边都缓存用户指定的数量的页面
//如果当前没有页面,则我们啥也不需要做
if (curItem != null) {
float extraWidthLeft = 0.f;
//左边的页面
int itemIndex = curIndex - 1;
//如果当前页面左边有页面,则将左边页面对应的ItemInfo取出,否则左边页面的ItemInfo为null
ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
//保存显示区域的宽度
final int clientWidth = getClientWidth();
//算出左边页面需要的宽度,注意,这里的宽度是指实际宽度与可视区域宽度比例,
// 即实际宽度=leftWidthNeeded*clientWidth
final float leftWidthNeeded = clientWidth <= 0 ? 0 :
2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;
//从当前页面左边第一个页面开始,左边的页面进行遍历
for (int pos = mCurItem - 1; pos >= 0; pos--) {
//如果左边的宽度超过了所需的宽度,并且当前当前页面位置比第一个缓存页面位置小
//这说明这个页面需要Destroy掉
if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {
//如果左边已经没有页面了,跳出循环
if (ii == null) {
break;
}
//将当前页面destroy掉
if (pos == ii.position && !ii.scrolling) {
mItems.remove(itemIndex);
//回调PagerAdapter的destroyItem
mAdapter.destroyItem(this, pos, ii.object);
if (DEBUG) {
Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
" view: " + ((View) ii.object));
}
//由于mItems删除了一个元素
//需要将索引减一
itemIndex--;
curIndex--;
ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
}
} else if (ii != null && pos == ii.position) {
//如果当前位置是需要缓存的位置,并且这个位置上的页面已经存在
//则将左边宽度加上当前位置的页面
extraWidthLeft += ii.widthFactor;
//mItems往左遍历
itemIndex--;
//ii设置为当前遍历的页面的左边一个页面
ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
} else {//如果当前位置是需要缓存,并且这个位置没有页面
//需要添加一个ItemInfo,而addNewItem是通过PagerAdapter的instantiateItem获取对象
ii = addNewItem(pos, itemIndex + 1);
//将左边宽度加上当前位置的页面
extraWidthLeft += ii.widthFactor;
//由于新加了一个元素,当前的索引号需要加1
curIndex++;
//ii设置为当前遍历的页面的左边一个页面
ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
}
}
//同理,右边需要添加缓存的页面
//......
// 省略右边添加缓存页面代码
//......
calculatePageOffsets(curItem, curIndex, oldCurInfo);
}
if (DEBUG) {
Log.i(TAG, "Current page list:");
for (int i = 0; i < mItems.size(); i++) {
Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
}
}
//回调PagerAdapter的setPrimaryItem,告诉PagerAdapter当前显示的页面
mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
//回调PagerAdapter的finishUpdate,告诉PagerAdapter页面更新结束
mAdapter.finishUpdate(this);
//检查页面的宽度是否测量,如果页面的LayoutParams数据没有设定,则去重新设定好
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
lp.childIndex = i;
if (!lp.isDecor && lp.widthFactor == 0.f) {
// 0 means requery the adapter for this, it doesn't have a valid width.
final ItemInfo ii = infoForChild(child);
if (ii != null) {
lp.widthFactor = ii.widthFactor;
lp.position = ii.position;
}
}
}
//重新对页面排序
sortChildDrawingOrder();
//如果ViewPager被设定为可获焦的,则将当前显示的页面设定为获焦
if (hasFocus()) {
View currentFocused = findFocus();
ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
if (ii == null || ii.position != mCurItem) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
ii = infoForChild(child);
if (ii != null && ii.position == mCurItem) {
if (child.requestFocus(View.FOCUS_FORWARD)) {
break;
}
}
}
}
}
}
2.setCurrentItemInternal()
//此方法跳到相应的页面
void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
if (mAdapter == null || mAdapter.getCount() <= 0) {
setScrollingCacheEnabled(false);
return;
}
if (!always && mCurItem == item && mItems.size() != 0) {
setScrollingCacheEnabled(false);
return;
}
if (item < 0) {
item = 0;
} else if (item >= mAdapter.getCount()) {
item = mAdapter.getCount() - 1;
}
final int pageLimit = mOffscreenPageLimit;
if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
// We are doing a jump by more than one page. To avoid
// glitches, we want to keep all current pages in the view
// until the scroll ends.
for (int i = 0; i < mItems.size(); i++) {
mItems.get(i).scrolling = true;
}
}
final boolean dispatchSelected = mCurItem != item;
if (mFirstLayout) {
// We don't have any idea how big we are yet and shouldn't have any pages either.
// Just set things up and let the pending layout handle things.
mCurItem = item;
if (dispatchSelected) {
dispatchOnPageSelected(item);
}
requestLayout();
} else {
//创建item位置的页面
populate(item);
//滑动到item页面
scrollToItem(item, smoothScroll, velocity, dispatchSelected);
}
}
private void scrollToItem(int item, boolean smoothScroll, int velocity,
boolean dispatchSelected) {
final ItemInfo curInfo = infoForPosition(item);
int destX = 0;
if (curInfo != null) {
final int width = getClientWidth();
destX = (int) (width * Math.max(mFirstOffset,
Math.min(curInfo.offset, mLastOffset)));
}
if (smoothScroll) {
//调用这个滑动方法
smoothScrollTo(destX, 0, velocity);
if (dispatchSelected) {
dispatchOnPageSelected(item);
}
} else {
if (dispatchSelected) {
dispatchOnPageSelected(item);
}
completeScroll(false);
scrollTo(destX, 0);
pageScrolled(destX);
}
}
void smoothScrollTo(int x, int y, int velocity) {
if (getChildCount() == 0) {
// Nothing to do.
setScrollingCacheEnabled(false);
return;
}
int sx;
boolean wasScrolling = (mScroller != null) && !mScroller.isFinished();
if (wasScrolling) {
// We're in the middle of a previously initiated scrolling. Check to see
// whether that scrolling has actually started (if we always call getStartX
// we can get a stale value from the scroller if it hadn't yet had its first
// computeScrollOffset call) to decide what is the current scrolling position.
sx = mIsScrollStarted ? mScroller.getCurrX() : mScroller.getStartX();
// And abort the current scrolling.
mScroller.abortAnimation();
setScrollingCacheEnabled(false);
} else {
sx = getScrollX();
}
int sy = getScrollY();
int dx = x - sx;
int dy = y - sy;
if (dx == 0 && dy == 0) {
completeScroll(false);
// 又是这个方法,又是你我认识
populate();
setScrollState(SCROLL_STATE_IDLE);
return;
}
setScrollingCacheEnabled(true);
setScrollState(SCROLL_STATE_SETTLING);
final int width = getClientWidth();
final int halfWidth = width / 2;
final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);
final float distance = halfWidth + halfWidth *
distanceInfluenceForSnapDuration(distanceRatio);
// 页面切换的持续时间
int duration;
velocity = Math.abs(velocity);
if (velocity > 0) {
duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
} else {
final float pageWidth = width * mAdapter.getPageWidth(mCurItem);
final float pageDelta = (float) Math.abs(dx) / (pageWidth + mPageMargin);
duration = (int) ((pageDelta + 1) * 100);
}
// 切换页面的时间
duration = Math.min(duration, MAX_SETTLE_DURATION);
// Reset the "scroll started" flag. It will be flipped to true in all places
// where we call computeScrollOffset().
mIsScrollStarted = false;
// 切换页面最终会调用Scroller的startScroll()方法
mScroller.startScroll(sx, sy, dx, dy, duration);
ViewCompat.postInvalidateOnAnimation(this);
}
创建ItemView内部方法: mAdapter.instantiateItem(this, position)
销毁ItemView内部方法: mAdapter.destroyItem(this, pos, ii.object);
切换页面执行动:mScroller.startScroll(sx, sy, dx, dy, duration);
实现一个图片轮播
1.我们先自定义一个viewPager和PagerAdapter。这个PagerAdapter能返回一个ImageView作为itemView(通过adapter返回自定义View),同时这个viewPager通过反射改变图片切换的速度。
2.之后把我们自定义的ViewPager和指示内容布局,并通过inflate()加载到继承RelativeLayout自定义View-BannerView中。通过给viewpager监听滑动来显示描述和点指示
使用
用户端,只需要加载BannerView布局,并设置好参数。
<declare-styleable name="BannerView">
<!-- 点选中的颜色值 -->
<attr name="dotIndicatorFocus" format="color|reference" />
<!-- 点默认的颜色值 -->
<attr name="dotIndicatorNormal" format="color|reference" />
<!-- 点的大小 -->
<attr name="dotSize" format="dimension"/>
<!-- 点的距离 -->
<attr name="dotDistance" format="dimension"/>
<!-- 点的位置 -->
<attr name="dotGravity" format="enum" >
<enum name="center" value="0" />
<enum name="left" value="1" />
<enum name="right" value="-1" />
</attr>
<!-- 底部颜色 -->
<attr name="bottomColor" format="color" />
<!-- 定义宽高比 -->
<attr name="widthProportion" format="float" />
<attr name="heigthProportion" format="float" />
</declare-styleable>
<com.haiming.myapplication.banner.BannerView
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="@+id/banner_view"
app:dotSize="8dp"
app:dotDistance="10dp"
app:dotGravity="right"
app:bottomColor="@color/colorAccent"
app:dotIndicatorFocus="@color/red"
app:dotIndicatorNormal="@color/white"
app:widthProportion="7"
app:heigthProportion="6"
/>
public class MainActivity extends AppCompatActivity {
private BannerView mBannerView;
private String[] url = {
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1583608357897&di=87c5801dc8f155c54b137a2288585943&imgtype=0&src=http%3A%2F%2Fc.hiphotos.baidu.com%2Fzhidao%2Fpic%2Fitem%2Fd009b3de9c82d1587e249850820a19d8bd3e42a9.jpg",
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1583608357896&di=3e48f71c2d4b942e7a292e9ee228ba68&imgtype=0&src=http%3A%2F%2Fe.hiphotos.baidu.com%2Fzhidao%2Fpic%2Fitem%2Fd62a6059252dd42a1c362a29033b5bb5c9eab870.jpg",
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1583608357895&di=42e050010f27198d7a831370a854ff4c&imgtype=0&src=http%3A%2F%2Fbig5.wallcoo.com%2Fanimal%2Ffly_and_freedom%2Fimages%2F0Vol_096_DY164.jpg",
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1583608357895&di=64a4126a298344bdd2d1ae9e6f469632&imgtype=0&src=http%3A%2F%2Fimg1.gtimg.com%2Frushidao%2Fpics%2Fhv1%2F20%2F108%2F1744%2F113431160.jpg"
};
private String[] desc = {
"游戏","狮子","鸟","美景"
};
public BannerAdapter<String> mBannerAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.banner_layout);
mBannerView= findViewById(R.id.banner_view);
initBanner();
}
private void initBanner() {
if(mBannerAdapter == null){
mBannerAdapter = new BannerAdapter<String>(Arrays.asList(url)) {
@Override
public View getView(int position, View convertView, List<String> dataList) {
ImageView bannerIv = null;
if(convertView == null){
bannerIv = new ImageView(MainActivity.this);
}else {
bannerIv= (ImageView) convertView;
}
int curPosition = position%getCount();
//利用第三方的工具加载图片 glide
String path = dataList.get(curPosition);
Glide.with(MainActivity.this)
.load(path)
.placeholder(R.mipmap.ic_launcher)
.into(bannerIv);
return bannerIv;
}
@Override
public int getCount() {
return url.length;
}
};
}
mBannerAdapter.setDescription(Arrays.asList(desc));
mBannerView.setAdapter(mBannerAdapter);
}
}
自定义viewPager和PagerAdapter
public class BannerViewPager<T> extends ViewPager {
//数据的资源
private List<T> mDataList;
//自定义adapter,主要是能自定义view
private BannerAdapter mAdapter;
//发送消息的Msg
private final int MSG = 0X0010;
//页面切换间隔时间
private int mCurDownTime = 3500;
//切换到指定的item,并重新发送消息
private Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
//每隔多少秒切换下一张图片
setCurrentItem((getCurrentItem()+1));
startRoll();
}
};
//继承Scroller,修改滑动时间
private BannerScroller mScroller;
//存放被销毁的View
private List<View> mCurrentView;
private Activity mActivity;
//管理activity的生命周期
Application.ActivityLifecycleCallbacks activityLifecycleCallbacks = new DefaultActivityLifecycleCallbacks() {
@Override
public void onActivityResumed(@NonNull Activity activity) {
//是不是监听当前的activity的生命周期
if(activity == getContext()) {
//开启轮播
handler.sendEmptyMessageDelayed(mCurDownTime, MSG);
}
}
@Override
public void onActivityPaused(@NonNull Activity activity) {
//是不是监听当前的activity的生命周期
if(activity == getContext()) {
//停止轮播
handler.removeMessages(MSG);
}
}
};
public BannerViewPager(@NonNull Context context) {
this(context, (AttributeSet) null);
}
public BannerViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public void setDataList(List<T> mDataList) {
this.mDataList = mDataList;
}
public List<T> getDataList() {
return mDataList;
}
private void initView(Context context) {
this.mActivity = (Activity) context;
//通过反射设置Scroller成另一个BannerScroller
try {
Field field = ViewPager.class.getDeclaredField("mScroller");
field.setAccessible(true);
mScroller = new BannerScroller(context);
field.set(this,mScroller);
} catch (Exception e) {
e.printStackTrace();
}
mCurrentView = new ArrayList<>();
}
//设置切换页面动画的持续时间
public void setScrollerDuration(int scrollerDuration){
mScroller.setDurationTime(scrollerDuration);
}
public void setAdapter(@Nullable BannerAdapter adapter) {
//自定义加载View的adpter
this.mAdapter=adapter;
// BannerPagerAdapter继承PagerAdapter,可以实现自定义加载view
setAdapter(new BannerPagerAdapter<T>(mDataList));
//管理activity的生命周期
mActivity.getApplication().registerActivityLifecycleCallbacks(activityLifecycleCallbacks);
}
/**
* 实现自动切换,发送消息
*/
public void startRoll(){
//清除消息
handler.removeMessages(MSG);
//消息 延迟发送
handler.sendEmptyMessageDelayed(MSG,mCurDownTime);
}
/**
* 销毁handler,避免内存泄漏
*/
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
handler.removeMessages(MSG);
handler=null;
mActivity.getApplication().unregisterActivityLifecycleCallbacks(activityLifecycleCallbacks);
}
/**
* veiwpager设置适配器
*/
private class BannerPagerAdapter<T> extends PagerAdapter{
//数据的资源
private List<T> mDataList;
public BannerPagerAdapter(List<T> dataList){
this.mDataList = dataList;
}
@Override
public int getCount() {
//为了实现无线循环 0-2的32次方
return Integer.MAX_VALUE;
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object;
}
/**
* 创建VeiwPager条目回调的方法
* @param container
* @param position
* @return
*/
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
// Adapter设计模式 为了完全让用户自定义
View bannerItemVeiw =mAdapter.getView(position,getConverst(),mDataList);
//添加ViewPager里面
container.addView(bannerItemVeiw);
return bannerItemVeiw;
}
/**
* 销毁条目回调的方法
* @param container
* @param position
* @param object
*/
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView((View) object);
mCurrentView.add((View) object);
}
//获取复用界面
private View getConverst() {
for(int i=0;i<mCurrentView.size();i++){
if(mCurrentView.get(i).getParent() == null){
return mCurrentView.get(i);
}
}
return null;
}
}
}
写一个可自定义itemView的adapter
public abstract class BannerAdapter<T> {
private List<T> mDataList;
private List<String> mDescription;
public BannerAdapter(List<T> dataList){
this.mDataList = dataList;
}
/**
* 根据位置获取viewpager的子view
* @param position
* @return
*/
public abstract View getView(int position,View convertView,List<T> dataList) ;
/**
* 返回所有子view的数量
* @return
*/
public abstract int getCount();
public List<T> getDataList() {
return mDataList;
}
public void setDescription(List<String> mDescription) {
this.mDescription = mDescription;
}
public List<String> getDescription() {
return mDescription;
}
}
更换页面滑动的速度
public class BannerScroller extends Scroller {
//动画持续的时间
private int mDurationTime = 850;
public BannerScroller(Context context) {
super(context);
}
public BannerScroller(Context context, Interpolator interpolator) {
super(context, interpolator);
}
public BannerScroller(Context context, Interpolator interpolator, boolean flywheel) {
super(context, interpolator, flywheel);
}
@Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
// 把持续时间传我们的方法
super.startScroll(startX, startY, dx, dy, mDurationTime);
}
public void setDurationTime(int time){
this.mDurationTime=time;
}
}
写一个轮播的布局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.haiming.myapplication.banner.BannerViewPager
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/banner_vp"
/>
<RelativeLayout
android:id="@+id/banner_bottom_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:padding="10dp"
>
<TextView
android:id="@+id/banner_desc_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textColor="@color/white"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:id="@+id/dot_container"
android:orientation="horizontal"
>
</LinearLayout>
</RelativeLayout>
</RelativeLayout>
自定义View加载我们上面的布局,并动态生成圆点,和viewpager的监听
/**
* if(mBannerAdapter == null){
* mBannerAdapter = new BannerAdapter<String>(Arrays.asList(url)) {
* @Override
* public View getView(int position, View convertView, List<String> dataList) {
* ImageView bannerIv = null;
* if(convertView == null){
* bannerIv = new ImageView(MainActivity.this);
* }else {
* bannerIv= (ImageView) convertView;
* }
* int curPosition = position%getCount();
* //利用第三方的工具加载图片 glide
* String path = dataList.get(curPosition);
* Glide.with(MainActivity.this)
* .load(path)
* .placeholder(R.mipmap.ic_launcher)
* .into(bannerIv);
* return bannerIv;
* }
* @Override
* public int getCount() {
* return url.length;
* }
* };
* }
* mBannerView.setAdapter(mBannerAdapter);
*/
public class BannerView extends RelativeLayout {
private Context mContext;
//轮播viewpager
private BannerViewPager mBannerViewPager;
//轮播的描述
private TextView descTextView;
//点的容器,里面addView圆点
private LinearLayout mDotContainerView;
//从外界拿到BannerAdapter,传给viewPager
private BannerAdapter mAdapter;
//描述
private List<String> desc ;
//底部的控件
private RelativeLayout relativeLayout;
//底部容器颜色默认透明
private int mButtomColor = Color.TRANSPARENT;
//自定义圆点的颜色
private Drawable mFocusDrawable = new ColorDrawable(Color.RED);
private Drawable mNormalDrawable = new ColorDrawable(Color.WHITE);
//自定义圆点的显示位置,默认中间
private int mDotGravity = 0;
//自定义圆点的大小
private int mDotSize = 8;
//自定义圆点的间据
private int mDotDistance = 8;
//获取宽高比例
private float mWidthProportion,mHeightProportion ;
//接口实例
private BannerItemClickListener mClickListener;
public BannerView(Context context) {
super(context,null);
initView(context);
}
public BannerView(Context context, AttributeSet attrs) {
super(context, attrs,0);
initView(context);
initAttribute(attrs);
}
public BannerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
initAttribute(attrs);
}
private void initView(Context context) {
//将这个布局加载这个view中
inflate(context, R.layout.ui_banner,this);
mBannerViewPager=findViewById(R.id.banner_vp);
descTextView = findViewById(R.id.banner_desc_tv);
mDotContainerView = findViewById(R.id.dot_container);
relativeLayout= findViewById(R.id.banner_bottom_view);
relativeLayout.post(new Runnable() {
@Override
public void run() {
relativeLayout.setBackgroundColor(mButtomColor);
}
});
this.mContext=context;
}
private void initAttribute(AttributeSet attrs) {
TypedArray array = mContext.obtainStyledAttributes(attrs,R.styleable.BannerView);
//获取点的位置
mDotGravity = array.getInt(R.styleable.BannerView_dotGravity,0);
//获取选择点的颜色
mFocusDrawable = array.getDrawable(R.styleable.BannerView_dotIndicatorFocus);
if(mFocusDrawable == null){
mFocusDrawable = new ColorDrawable(Color.RED);
}
Log.d("颜色","1");
//获取未选中的颜色
mNormalDrawable = array.getDrawable(R.styleable.BannerView_dotIndicatorNormal);
if(mNormalDrawable == null){
mNormalDrawable = new ColorDrawable(Color.WHITE);
}
//获取点的大小
mDotSize = (int) array.getDimension(R.styleable.BannerView_dotSize,dip2px(mDotSize));
//获取点的间距
mDotDistance = (int) array.getDimension(R.styleable.BannerView_dotDistance,dip2px(mDotDistance));
//获取底部颜色
mButtomColor = array.getColor(R.styleable.BannerView_bottomColor,mButtomColor);
//获取宽高比例
mWidthProportion = array.getFloat(R.styleable.BannerView_widthProportion,mWidthProportion);
mHeightProportion = array.getFloat(R.styleable.BannerView_heigthProportion,mHeightProportion);
array.recycle();
}
public void setAdapter(final BannerAdapter adapter){
this.mAdapter=adapter;
this.desc = adapter.getDescription();
final int count = mAdapter.getCount();
mBannerViewPager.setDataList(mAdapter.getDataList());
mBannerViewPager.setAdapter(adapter);
//初始化点的指示器,默认不显示
relativeLayout.setVisibility(VISIBLE);
initDotIndicator();
mBannerViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
int curPosition = position%mAdapter.getCount();
//改变圆点颜色
relativeLayout.setVisibility(VISIBLE);
pageSelect(curPosition);
//显示描述,默认不显示
relativeLayout.setVisibility(VISIBLE);
if(desc != null && desc.size()==count){
descTextView.setText(desc.get(curPosition));
}
if(mClickListener != null){
mClickListener.click(curPosition);
}
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
//动态设置高度
setPicHeight();
//开始通过handler发送消息
startRoll();
}
/**
* 动态设置高度
*/
private void setPicHeight() {
//动态指定宽高,计算高度
WindowManager manager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metrics = new DisplayMetrics();
manager.getDefaultDisplay().getMetrics(metrics);
int width = metrics.widthPixels;
if(mWidthProportion == 0 || mHeightProportion ==0){
return;
}
int heigth = (int) ((width*mHeightProportion)/mWidthProportion);
Log.d("高度","heigth="+heigth+" width="+width+" mWidthProportion="+mWidthProportion+" mHeightProportion"+mHeightProportion);
getLayoutParams().height=heigth;
}
/**
* 页面切换的回调
* @param position
*/
private int mCurrentPosition =0;
private void pageSelect(int position) {
//把原来的位置颜色设置默认
DotIndicatorView oldIndicatorView= (DotIndicatorView) mDotContainerView.getChildAt(mCurrentPosition);
oldIndicatorView.setDrawable(mNormalDrawable);
//当前位置的圆点变色
mCurrentPosition = position; //从0-2的32次方
DotIndicatorView currentIndicatorView= (DotIndicatorView) mDotContainerView.getChildAt(position%mAdapter.getCount());
currentIndicatorView.setDrawable(mFocusDrawable);
}
private boolean showDesc =false;
public void setshowDesc(boolean showDesc){
this.showDesc=showDesc;
}
// public boolean showDesc(){
// return showDesc;
// }
private boolean showDotIndicator =false;
public void showDotIndicator(boolean showDotIndicator){
this.showDotIndicator=showDotIndicator;
}
// public boolean showDotIndicator(){
// return showDotIndicator;
// }
/**
* 根据适配器的count创建圆点并增加至容器
*/
private void initDotIndicator() {
int count = mAdapter.getCount();
mDotContainerView.setGravity(getDotGravity());
for(int i=0;i<count;i++){
//不断的往点的指示器添加圆点
DotIndicatorView dotIndicatorView = new DotIndicatorView(mContext);
//给圆点设置LayoutParams
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mDotSize,mDotSize);
params.leftMargin=params.rightMargin= mDotDistance;
dotIndicatorView.setLayoutParams(params);
//默认选择第一张
if(i==0){
dotIndicatorView.setDrawable(mFocusDrawable);
}else {
dotIndicatorView.setDrawable(mNormalDrawable);
}
//往容器里增加圆点
Log.d("添加","1");
mDotContainerView.addView(dotIndicatorView);
}
}
/**
* 根据mDotGravity值判断Gravity的位置
* @return
*/
private int getDotGravity() {
switch (mDotGravity){
case 0:
return Gravity.CENTER;
case -1:
return Gravity.RIGHT;
case 1:
return Gravity.LEFT;
}
return Gravity.CENTER;
}
public void startRoll() {
mBannerViewPager.startRoll();
}
/**
* 把dip转成px
* @param dip
* @return
*/
private int dip2px(int dip) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP
,dip,getResources().getDisplayMetrics());
}
public void setBannerItemClickListener(BannerItemClickListener clickListener){
this.mClickListener = clickListener;
}
//点击回调监听
public interface BannerItemClickListener{
void click(int position);
}
}
自动播放:
- Time实现一个计时器
- Handler发送消息
- start Thread() 开启一个子线程
改变viewPager的切换速率:
- 通过改变duration调整速率,但是duration是局部变量,我们拿不到。
mScroller.startScroll(sx, sy, dx, dy, duration); - 那我们通过Scroller来设置,但是Scroller是声明为private,我们要通过反射来拿到。
网友评论