在android应用中很多情况都要用到图片或或者广告的轮播效果,包括可以自动切换和手动切换以及指示器,这篇文章将手把手教你自己实现这个效果,网上有较多很完善的框架也实现了效果,但在实际的项目中总会遇到各种各样奇葩的问题,还有一些兼容的问题,我不需要兼容到2.3,所以也不需要用到nineoldandroid,还有些动画效果很多都用不到,和其他的布局有冲突等系列问题,所以,打造一款属于自己的android滑动器还是很有必要的,知其然,知其所以然。
我们首先想到的就是使用ViewPager来实现效果,ViewPager可以做到图片切换,而且只能是手动的,其他的效果我们就要一点一点的加了,如轮播效果,指示器,自动切换,还有动画效果,下面就解决一个个的问题。
轮播效果实现
原生的ViewPager是不支持轮播效果的,滑动到第一页或者滑动到最后一页就不能再滑了,感觉不是那么的舒服,现在做一款app怎么能不支持轮播效果呢,这个问题网上有一种解决方案,无需改原生的ViewPager,只需要重写PagerAdapter就行了,看关键代码
public class LoopPagerAdapter extends PagerAdapter {
private List<View> views;
public LoopPagerAdapter(List<View> views) {
this.views = views;
}
@Override
public int getCount() {
return Integer.MAX_VALUE;//2147483647
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
View view = views.get(position % views.size());
if (container.equals(view.getParent())) {
container.removeView(view);
}
container.addView(view);
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
}
}
其实轮播是假的,但是也可以做到以假乱真的效果,count设置的足够大,一般人滑不了这么多,但不管怎么样,这也算一种解决方案
使用
public class MainActivity extends ActionBarActivity {
private ViewPager pager;
private LoopPagerAdapter loopPagerAdapter;
private List<View> views = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pager = (ViewPager) findViewById(R.id.pager);
views.add(getView(R.mipmap.img1));
views.add(getView(R.mipmap.img2));
views.add(getView(R.mipmap.img3));
loopPagerAdapter = new LoopPagerAdapter(views);
pager.setAdapter(loopPagerAdapter);
//设置当前item到Integer.MAX_VALUE中间的一个值,看起来像无论是往前滑还是往后滑都是ok的
//如果不设置,用户往左边滑动的时候已经划不动了
pager.setCurrentItem(Integer.MAX_VALUE / 2 - Integer.MAX_VALUE / 2 % views.size());
}
private View getView(int res) {
ImageView imageView = new ImageView(this);
//利用glide加载图片比原始的setImageResource占用内存小很多
Glide.with(this).load(res).into(imageView);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
return imageView;
}
效果图
简单的轮播效果就出来了,效果还不是很明显,下面添加指示器
添加指示器
顾名思义,提示图片的个数以及标识当前图片位置,可以通过代码绘制形状,也可以使用图片,下面开始我就要对滑动器进行封装成一个View,新建一个SliderLayout
public class SliderLayout extends RelativeLayout {
private ViewPager pager;
//指示器容器
private LinearLayout indicatorContainer;
private Drawable unSelectedDrawable;
private Drawable selectedDrawable;
public SliderLayout(Context context) {
super(context);
initView();
}
public SliderLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public SliderLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
//初始化pager
pager = new ViewPager(getContext());
//添加viewpager到SliderLayout
addView(pager);
//初始化indicatorContainer
indicatorContainer = new LinearLayout(getContext());
RelativeLayout.LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
//设置指示器的方位,如右下
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
//设置margin
params.setMargins(dpToPx(10), dpToPx(10), dpToPx(10), dpToPx(10));
//添加指示器容器布局到SliderLayout
addView(indicatorContainer, params);
//绘制未选中状态图形
LayerDrawable unSelectedLayerDrawable;
LayerDrawable selectedLayerDrawable;
GradientDrawable unSelectedGradientDrawable;
unSelectedGradientDrawable = new GradientDrawable();
unSelectedGradientDrawable.setShape(GradientDrawable.OVAL);
unSelectedGradientDrawable.setColor(Color.GRAY);
unSelectedGradientDrawable.setSize(dpToPx(8), dpToPx(8));
unSelectedLayerDrawable = new LayerDrawable(new Drawable[]{unSelectedGradientDrawable});
unSelectedDrawable = unSelectedLayerDrawable;
//绘制选中状态图形
GradientDrawable selectedGradientDrawable;
selectedGradientDrawable = new GradientDrawable();
selectedGradientDrawable.setShape(GradientDrawable.OVAL);
selectedGradientDrawable.setColor(Color.RED);
selectedGradientDrawable.setSize(dpToPx(8), dpToPx(8));
selectedLayerDrawable = new LayerDrawable(new Drawable[]{selectedGradientDrawable});
selectedDrawable = selectedLayerDrawable;
}
//添加本地图片路径
public void setViewRes(List<Integer> viewRes) {
List<View> views = new ArrayList<>();
for (Integer res : viewRes) {
ImageView imageView = new ImageView(getContext());
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
Glide.with(getContext()).load(res).into(imageView);
views.add(imageView);
}
setViews(views);
}
//添加网络图片路径
public void setViewUrls(List<String> urls) {
List<View> views = new ArrayList<>();
for (String url : urls) {
ImageView imageView = new ImageView(getContext());
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
Glide.with(getContext()).load(url).into(imageView);
views.add(imageView);
}
setViews(views);
}
//添加任意View视图
public void setViews(final List<View> views) {
//初始化指示器,并添加到指示器容器布局
for (int i = 0; i < views.size(); i++) {
ImageView indicator = new ImageView(getContext());
indicator.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
indicator.setPadding(dpToPx(3), dpToPx(3), dpToPx(3), dpToPx(3));
indicatorContainer.addView(indicator);
}
LoopPagerAdapter pagerAdapter = new LoopPagerAdapter(views);
pager.setAdapter(pagerAdapter);
//设置当前item到Integer.MAX_VALUE中间的一个值,看起来像无论是往前滑还是往后滑都是ok的
//如果不设置,用户往左边滑动的时候已经划不动了
int currentItem = Integer.MAX_VALUE / 2 - Integer.MAX_VALUE / 2 % views.size();
pager.setCurrentItem(currentItem);
switchIndicator(currentItem % views.size());
//添加监听器
pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
switchIndicator(position % views.size());
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
/** * 切换指示器状态 * @param currentPosition 当前位置 */
private void switchIndicator(int currentPosition) {
for (int i = 0; i < indicatorContainer.getChildCount(); i++) {
((ImageView) indicatorContainer.getChildAt(i)).setImageDrawable(i == currentPosition ? selectedDrawable : unSelectedDrawable);
}
}
/** * 单位转换将dp转化为px * @param dp dp值 * @return 返回px */
private int dpToPx(int dp) {
DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
return (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, displayMetrics) + 0.5);
}
}
xml中添加
<com.example.dongjunkun.myapplication.SliderLayout
android:id="@+id/sliderLayout"
android:layout_width="match_parent"
android:layout_height="200dp"
/>
代码中使用
public class MainActivity extends ActionBarActivity {
private SliderLayout sliderLayout;
private List<Integer> viewRes = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sliderLayout = (SliderLayout) findViewById(R.id.sliderLayout);
viewRes.add(R.mipmap.img1);
viewRes.add(R.mipmap.img2);
viewRes.add(R.mipmap.img3);
sliderLayout.setViewRes(viewRes);
}
}
效果演示
到这里指示器已经ok了,下面实现自动切换
自动切换
用Handler实现计时器
public class SliderLayout extends RelativeLayout implements android.os.Handler.Callback {
public static final int DELAY_MILLIS = 4000;
private ViewPager pager;
//指示器容器
private LinearLayout indicatorContainer;
private Drawable unSelectedDrawable;
private Drawable selectedDrawable;
private int WHAT_AUTO_PLAY = 1000;
private Handler handler;
private boolean isAutoPlay = false;
public SliderLayout(Context context) {
super(context);
initView();
}
public SliderLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public SliderLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
handler = new Handler(this);
//初始化pager
pager = new ViewPager(getContext());
//添加viewpager到SliderLayout
addView(pager);
//初始化indicatorContainer
indicatorContainer = new LinearLayout(getContext());
RelativeLayout.LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
//设置指示器的方位,如右下
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
//设置margin
params.setMargins(dpToPx(10), dpToPx(10), dpToPx(10), dpToPx(10));
//添加指示器容器布局到SliderLayout
addView(indicatorContainer, params);
//绘制未选中状态图形
LayerDrawable unSelectedLayerDrawable;
LayerDrawable selectedLayerDrawable;
GradientDrawable unSelectedGradientDrawable;
unSelectedGradientDrawable = new GradientDrawable();
unSelectedGradientDrawable.setShape(GradientDrawable.OVAL);
unSelectedGradientDrawable.setColor(Color.GRAY);
unSelectedGradientDrawable.setSize(dpToPx(8), dpToPx(8));
unSelectedLayerDrawable = new LayerDrawable(new Drawable[]{unSelectedGradientDrawable});
unSelectedDrawable = unSelectedLayerDrawable;
//绘制选中状态图形
GradientDrawable selectedGradientDrawable;
selectedGradientDrawable = new GradientDrawable();
selectedGradientDrawable.setShape(GradientDrawable.OVAL);
selectedGradientDrawable.setColor(Color.RED);
selectedGradientDrawable.setSize(dpToPx(8), dpToPx(8));
selectedLayerDrawable = new LayerDrawable(new Drawable[]{selectedGradientDrawable});
selectedDrawable = selectedLayerDrawable;
}
//添加本地图片路径
public void setViewRes(List<Integer> viewRes) {
List<View> views = new ArrayList<>();
for (Integer res : viewRes) {
ImageView imageView = new ImageView(getContext());
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
Glide.with(getContext()).load(res).into(imageView);
views.add(imageView);
}
setViews(views);
}
//添加网络图片路径
public void setViewUrls(List<String> urls) {
List<View> views = new ArrayList<>();
for (String url : urls) {
ImageView imageView = new ImageView(getContext());
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
Glide.with(getContext()).load(url).into(imageView);
views.add(imageView);
}
setViews(views);
}
//添加任意View视图
public void setViews(final List<View> views) {
//初始化指示器,并添加到指示器容器布局
for (int i = 0; i < views.size(); i++) {
ImageView indicator = new ImageView(getContext());
indicator.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
indicator.setPadding(dpToPx(3), dpToPx(3), dpToPx(3), dpToPx(3));
indicatorContainer.addView(indicator);
}
LoopPagerAdapter pagerAdapter = new LoopPagerAdapter(views);
pager.setAdapter(pagerAdapter);
//设置当前item到Integer.MAX_VALUE中间的一个值,看起来像无论是往前滑还是往后滑都是ok的
//如果不设置,用户往左边滑动的时候已经划不动了
int currentItem = Integer.MAX_VALUE / 2 - Integer.MAX_VALUE / 2 % views.size();
pager.setCurrentItem(currentItem);
switchIndicator(currentItem % views.size());
//添加监听器
pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
switchIndicator(position % views.size());
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
startAutoPlay();
}
@Override
public boolean handleMessage(Message msg) {
if (msg.what == WHAT_AUTO_PLAY) {
pager.setCurrentItem(pager.getCurrentItem() + 1, true);
handler.sendEmptyMessageDelayed(WHAT_AUTO_PLAY, DELAY_MILLIS);
}
return false;
}
/** * 开始自动轮播 */
public void startAutoPlay() {
if (!isAutoPlay) {
handler.sendEmptyMessageDelayed(WHAT_AUTO_PLAY, DELAY_MILLIS);
isAutoPlay = true;
}
}
/** * 停止自动轮播 */
public void stopAutoPlay() {
if (isAutoPlay) {
handler.removeMessages(WHAT_AUTO_PLAY);
isAutoPlay = false;
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
stopAutoPlay();
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
startAutoPlay();
break;
}
return super.dispatchTouchEvent(ev);
}
/** * 切换指示器状态 * * @param currentPosition 当前位置 */
private void switchIndicator(int currentPosition) {
for (int i = 0; i < indicatorContainer.getChildCount(); i++) {
((ImageView) indicatorContainer.getChildAt(i)).setImageDrawable(i == currentPosition ? selectedDrawable : unSelectedDrawable);
}
}
/** * 单位转换将dp转化为px * * @param dp dp值 * @return 返回px */
private int dpToPx(int dp) {
DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
return (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, displayMetrics) + 0.5);
}
}
经过修改SliderLayout布局已经添加了自动轮播的效果
最后就是添加各种各样的动画了,网上例子比较多,简单教大家下窍门
//设置动画
pager.setPageTransformer(true, new ViewPager.PageTransformer() {
@Override
public void transformPage(View page, float position) {
if (position < -1.0f) {
// [-Infinity,-1)
// 左边页面处于不可见状态
} else if (position <= 0.0f) {
// [-1,0]
// 左边页面显示
} else if (position <= 1.0f) {
// (0,1]
//左边页面显示
} else {
// (1,+Infinity]
// 右边页面处于不可见状态
}
}
});
这个一定要理解,推荐大家看慕课网千变万化的ViewPager教程
简单演示
完整demo已上传到github SliderLayout