Android轮播图(指示器随页面滑动)
2017-5-19 liuyingcong 安卓开发
先上图:
与喜马拉雅FM中轮播图效果是一样的,切换平缓,并且可以设置切换时间,以及每个页面停留的时间间隔,最大的特点是页面指示器随页面滚动,到最后一个页面时,滑动到一半时圆点指示到第一个页面,触摸停止,应用glide加载网络图片,可以设置默认图片和缓存以及失败图片,每一个页面具备点击事件。
项目在github上:https://github.com/986558210/Banner
代码如下:
核心代码Banner.java:
package com.example.banner; import android.content.Context; import android.content.res.TypedArray; import android.os.Handler; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ImageView.ScaleType; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.Scroller; import com.bumptech.glide.Glide; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.example.banner.listener.OnBannerClickListener; import com.example.banner.listener.OnLoadImageListener; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; public class Banner extends FrameLayout implements ViewPager.OnPageChangeListener { private int mIndicatorWidth; private int mIndicatorHeight; private int mIndicatorMargin; private int mDelayTime; private int mDuration; private boolean isAutoPlay; private int mIndicatorSelectedResId; private int mIndicatorUnselectedResId; private int defaultImage = -1; private int count = 0; private int currentItem; private int gravity = -1; private int scaleType = 0; private List<ImageView> imageViews;//图片集合 private Context context; private ViewPager viewPager; private LinearLayout indicator_bottom, indicator_top; private Handler handler = new Handler(); private OnLoadImageListener imageListener; private BannerPagerAdapter adapter; private ViewPager.OnPageChangeListener mOnPageChangeListener; private OnBannerClickListener listener; private ImageView imageView_sel; private LinearLayout.LayoutParams mLayoutParams; public Banner(Context context) { this(context, null); } public Banner(Context context, AttributeSet attrs) { this(context, attrs, 0); } public Banner(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); this.context = context; imageViews = new ArrayList<>(); initView(context, attrs); } private void initView(Context context, AttributeSet attrs) { View view = LayoutInflater.from(context).inflate(R.layout.banner, this, true); viewPager = (ViewPager) view.findViewById(R.id.viewpager); //指示器的底层点的父布局 indicator_bottom = (LinearLayout) view.findViewById(R.id.indicator_bottom); //上层滑动的点的父布局 indicator_top = (LinearLayout) view.findViewById(R.id.indicator_top); handleTypedArray(context, attrs); initViewPagerScroll(); } private void handleTypedArray(Context context, AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Banner); mIndicatorWidth = typedArray.getDimensionPixelSize(R.styleable.Banner_indicator_width, 7); mIndicatorHeight = typedArray.getDimensionPixelSize(R.styleable.Banner_indicator_height, 7); mIndicatorMargin = typedArray.getDimensionPixelSize(R.styleable.Banner_indicator_margin, 5); mIndicatorSelectedResId = typedArray.getResourceId(R.styleable.Banner_indicator_drawable_selected, R.drawable.gray_radius); mIndicatorUnselectedResId = typedArray.getResourceId(R.styleable.Banner_indicator_drawable_unselected, R.drawable.white_radius); scaleType = typedArray.getInt(R.styleable.Banner_image_scale_type, 0); defaultImage = typedArray.getResourceId(R.styleable.Banner_default_image, -1); mDelayTime = typedArray.getInt(R.styleable.Banner_delay_time, 2000); mDuration = typedArray.getInt(R.styleable.Banner_duration, 700); isAutoPlay = typedArray.getBoolean(R.styleable.Banner_is_auto_play, true); typedArray.recycle(); } /** * 为VP添加滚动器,为了控制切换的时间 */ private void initViewPagerScroll() { try { Field mField = ViewPager.class.getDeclaredField("mScroller"); mField.setAccessible(true); ViewPagerScroller mScroller = new ViewPagerScroller(viewPager.getContext()); mField.set(viewPager, mScroller); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } /** * 添加图片的方法 * * @param imagesUrl 图片的地址集合 * @param imageListener */ public void setImageList(List<?> imagesUrl, OnLoadImageListener imageListener) { if (imagesUrl == null || imagesUrl.size() <= 0) { return; } count = imagesUrl.size(); imageViews.clear(); createIndicator(); for (int i = 1; i <= count; i++) { ImageView iv = new ImageView(context); if (scaleType == 0) iv.setScaleType(ScaleType.FIT_XY); else iv.setScaleType(ScaleType.CENTER_CROP); Object url; if (i == 0) { url = imagesUrl.get(count - 1); } else if (i == count + 1) { url = imagesUrl.get(0); } else { url = imagesUrl.get(i - 1); } imageViews.add(iv); if (imageListener != null) { imageListener.OnLoadImage(iv, url); } else { if (defaultImage != -1) Glide.with(context) .load(url) .diskCacheStrategy(DiskCacheStrategy.ALL) .crossFade().placeholder(defaultImage) .error(defaultImage) .into(iv); else Glide.with(context) .load(url) .crossFade() .into(iv); } } setData(); } private void setData() { currentItem = Integer.MAX_VALUE / 2 - Integer.MAX_VALUE / 2 % count; if (adapter == null) { adapter = new BannerPagerAdapter(); viewPager.setAdapter(adapter); } else { adapter.notifyDataSetChanged(); } viewPager.setFocusable(true); viewPager.setCurrentItem(currentItem); viewPager.addOnPageChangeListener(this); if (gravity != -1) { indicator_bottom.setGravity(gravity); indicator_top.setGravity(gravity); } if (isAutoPlay) startAutoPlay(); } private void createIndicator() { //创建底部的点 indicator_bottom.removeAllViews(); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mIndicatorWidth, mIndicatorHeight); params.leftMargin = mIndicatorMargin; params.rightMargin = mIndicatorMargin; for (int i = 0; i < count; i++) { ImageView imageView = new ImageView(context); imageView.setScaleType(ScaleType.CENTER_CROP); imageView.setImageResource(mIndicatorUnselectedResId); indicator_bottom.addView(imageView, params); } //创建选中的点 indicator_top.removeAllViews(); imageView_sel = new ImageView(context); imageView_sel.setScaleType(ScaleType.CENTER_CROP); imageView_sel.setImageResource(mIndicatorSelectedResId); RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(count * mIndicatorWidth + (count - 1) * 2 * mIndicatorMargin, RelativeLayout.LayoutParams.WRAP_CONTENT); layoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL); indicator_top.setLayoutParams(layoutParams); indicator_top.addView(imageView_sel, new LinearLayout.LayoutParams(mIndicatorWidth, mIndicatorHeight)); } private void startAutoPlay() { handler.removeCallbacks(task); handler.postDelayed(task, mDelayTime); } public void stopAutoPlay() { handler.removeCallbacks(task); } private final Runnable task = new Runnable() { @Override public void run() { if (count > 1 && isAutoPlay) { currentItem += 1; viewPager.setCurrentItem(currentItem); handler.postDelayed(task, mDelayTime); } } }; @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (isAutoPlay) { int action = ev.getAction(); if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_OUTSIDE) { startAutoPlay(); } else if (action == MotionEvent.ACTION_DOWN) { stopAutoPlay(); } } return super.dispatchTouchEvent(ev); } class BannerPagerAdapter extends PagerAdapter { @Override public int getCount() { return Integer.MAX_VALUE; } @Override public boolean isViewFromObject(View arg0, Object arg1) { return arg0 == arg1; } @Override public Object instantiateItem(ViewGroup container, final int position) { container.addView(imageViews.get(position % count)); ImageView view = imageViews.get(position % count); if (listener != null) { view.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { listener.OnBannerClick(position); } }); } return view; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView(imageViews.get(position % count)); } } @Override public void onPageScrollStateChanged(int state) { currentItem = viewPager.getCurrentItem(); if (mOnPageChangeListener != null) { mOnPageChangeListener.onPageScrollStateChanged(state); } } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { if (mOnPageChangeListener != null) { mOnPageChangeListener.onPageScrolled(position, positionOffset, positionOffsetPixels); } //俩点之间的距离 int dis = (int) (indicator_bottom.getChildAt(1).getX() - indicator_bottom.getChildAt(0).getX()); mLayoutParams = (LinearLayout.LayoutParams) imageView_sel.getLayoutParams(); if(! (position % count == count - 1)) { mLayoutParams.leftMargin = Math.round(dis * (position % count + positionOffset)); }else if(positionOffset<=0.5){ mLayoutParams.leftMargin = Math.round(dis * (position % count)); }else { mLayoutParams.leftMargin = Math.round(0); } imageView_sel.setLayoutParams(mLayoutParams); } @Override public void onPageSelected(int position) { if (mOnPageChangeListener != null) { mOnPageChangeListener.onPageSelected(position); } } public void setOnBannerClickListener(OnBannerClickListener listener) { this.listener = listener; } class ViewPagerScroller extends Scroller { public ViewPagerScroller(Context context) { super(context); } @Override public void startScroll(int startX, int startY, int dx, int dy, int duration) { super.startScroll(startX, startY, dx, dy, mDuration); } @Override public void startScroll(int startX, int startY, int dx, int dy) { super.startScroll(startX, startY, dx, dy, mDuration); } } }布局文件banner.xml:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout 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/viewpager" android:layout_width="match_parent" android:layout_height="match_parent" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="7dp" android:layout_gravity="bottom"> <LinearLayout android:id="@+id/indicator_bottom" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:orientation="horizontal"/> <LinearLayout android:id="@+id/indicator_top" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:orientation="horizontal"/> </RelativeLayout> </FrameLayout>属性文件attr.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources> <declare-styleable name="Banner"> <attr name="default_image" format="reference" /> <attr name="delay_time" format="integer" /> <attr name="duration" format="integer" /> <attr name="is_auto_play" format="boolean" /> <attr name="indicator_width" format="dimension" /> <attr name="indicator_height" format="dimension" /> <attr name="indicator_margin" format="dimension" /> <attr name="indicator_drawable_selected" format="reference" /> <attr name="indicator_drawable_unselected" format="reference" /> <!--图片拉伸方式--> <attr name="image_scale_type" format="enum"> <enum name="fit_xy" value="0" /> <enum name="center_crop" value="1" /> </attr> </declare-styleable> </resources> 选中的点 gray_radius.xml:<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval"> <solid android:color="#fff" /> <corners android:radius="5dp" /> </shape>未选中的点 white_radius.xml:<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:innerRadius="3.15dp" android:shape="ring" android:thickness="0.35dp" android:useLevel="false" > <stroke android:width="0.35dp" android:color="#ffffffff" /> </shape>应用:
主界面代码MainActivity:
package com.example.banner; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private Banner mBanner;//轮播图 private List<String> mImages=new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mBanner = (Banner) findViewById(R.id.banner); mImages.add("http://fdfs.xmcdn.com/group26/M00/92/06/wKgJWFkVdbCSfGEaAALFo21GXxo938_android_large.jpg"); mImages.add("http://fdfs.xmcdn.com/group27/M01/8E/80/wKgJW1kVePLzosGjAAIg7qid8nw914_android_large.jpg"); mImages.add("http://fdfs.xmcdn.com/group26/M00/91/C9/wKgJRlkVf6Ow3z_EAAI7VV-UZew523_android_large.jpg"); mImages.add("http://fdfs.xmcdn.com/group26/M06/91/62/wKgJRlkVeS6z6RWDAAHb0lueHgY111_android_large.jpg"); mImages.add("http://fdfs.xmcdn.com/group27/M06/67/00/wKgJW1kSuNThZLySAARxLtV2RkI156_android_large.jpg"); mImages.add("http://fdfs.xmcdn.com/group27/M09/CD/9D/wKgJR1kH5j3wIeceAAJme6hHjsg639_android_large.jpg"); mImages.add("http://fdfs.xmcdn.com/group23/M05/B6/BA/wKgJNFiAc7Hhvbl1AAW3aA1GUqU096_android_large.jpg"); mBanner.setImageList(mImages, null); } }主界面布局activity_layout.xml:<?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" android:orientation="vertical"> <com.example.banner.Banner android:id="@+id/banner" android:layout_width="match_parent" android:layout_height="174.0dip" android:background="@drawable/banner_default" app:default_image="@drawable/banner_default" app:delay_time="2000" app:duration="2000" app:image_scale_type="fit_xy" app:indicator_drawable_selected="@drawable/selected_radius" app:indicator_drawable_unselected="@drawable/unselected_radius" app:indicator_height="7dip" app:indicator_margin="4dip" app:indicator_width="7dp" app:is_auto_play="true"/> </LinearLayout>项目在github上:https://github.com/986558210/Banner