Android:Viewpager——彻底解决广告条
一步一步实现广告条的制作,从最初的到添加描述和指示点,然后实现循环,以及自动循环。还有为每张视图添加点击事件……
中间处理了遇到的问题,比如:怎么实现左右都能循环?怎么解决出现的空白页面问题?加上自动循环之后需要考虑用户的哪些操作?等等。
最终效果:
最简单的使用 1、activity_main.xml
2、MainActivity.java
public class MainActivity extends AppCompatActivity {
private final int[] imagesIds = {
R.drawable.tazai,
R.drawable.tazaih,
R.drawable.sen,
R.drawable.chuya,
R.drawable.zhitian,
};
private ArrayList images;
@BindView(R.id.viewpager)
ViewPager viewpager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
setTitle("Demo");
images = new ArrayList();
for (int i = 0; i < imagesIds.length; i++) {
ImageView imageView = new ImageView(this);
imageView.setBackgroundResource(imagesIds[i]);
images.add(imageView);
}
viewpager.setAdapter(new MyPagerAdapter(images));
}
}
3、MyPagerAdapter.java
class MyPagerAdapter extends PagerAdapter {
private ArrayList images;
public MyPagerAdapter(ArrayList images) {
this.images = images;
}
@Override
public int getCount() {
return images.size();
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
ImageView imageView = images.get(position);
container.addView(imageView);
return imageView;
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object; //这个object为上一个方法返回的imageView。
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView((View)object);
}
}
写到这里,我们的界面已经可以滑动了,只是我们在使用的时候无法知道一共有多少个页面,以及我们现在处于哪个页面。下面我们来添加每个页面的描述和指示点。
有描述和指示点 1、activity_main.xml首先xml布局中,我们添加了垂直布局的LinearLayout,上面为一个TextView用来描述页面,下面是用一个水平布局的LinearLayout来放我们的指示点。这个指示点我们要动态添加。
2、指示点
我们用自定义的shape来表示指示点,分别有被选中时候的point_focued和没被选中时候的point_normal。最后用selector来设置不同选择状态下的相应的drawable值。
【res】——【drawable】——point_normal.xml
【res】——【drawable】——point_focued.xml
【res】——【drawable】——point_selector.xml
3、MainActivity.java
在每次为页面增加ImageView的时候,我们动态添加指示点。首先,指示点依point然用ImageView来显示,只是我们这次将背景设置为我们刚刚上面定义的point_selector。接着我们为point设置布局参数。由于我们的point本身是在线性布局中的,所以这里设置的参数一定要是: LinearLayout.LayoutParams,然后使用setLayoutParams()添加参数。最后使用addView()将point添加到LinearLayout布局中。
要能够描述当前界面,我们只需要把id=description的TextView设置为相对应的描述就可以了。要使指示点能够指示当前界面,本来是上一个界面的指示点亮,现在我们需要让上一个指示点灭,然后让本次的指示点亮,这就需要我们用一个变量“lastPointIndex”保存上一个指示点的位置。要实现循环的话,我们一会儿再讨论,这里只是实现顺次滑动。
那么我们要在哪里设置描述和指示点的亮灭呢?ViewPager为我们提供了
addOnPageChangeListener()
方法来监听页面的滑动。里面传入OnPageChangeListener
接口,该接口有三个方法我们可以去覆写。这里我们只实现onPageSelected()就可以了,顾名思义,这个就是当页面被选中的时候也就是页面完全显示在我们界面上的时候回调的方法。
public class MainActivity extends AppCompatActivity {
private final int[] imagesIds = {
R.drawable.tazai,
R.drawable.tazaih,
R.drawable.sen,
R.drawable.chuya,
R.drawable.zhitian,
};
private final String[] imagesDes = {
"为野犬,干杯!",
"最年少干部·太宰治",
"港口黑手党的首领",
"污浊了的忧伤之中",
"全世界最好的织田作"
};
private ArrayList images;
private int lastPointIndex;
@BindView(R.id.viewpager)
ViewPager viewpager;
@BindView(R.id.point_group)
LinearLayout pointGroup;
@BindView(R.id.description)
TextView description;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
setTitle("Demo");
images = new ArrayList();
for (int i = 0; i < imagesIds.length; i++) {
ImageView imageView = new ImageView(this);
imageView.setBackgroundResource(imagesIds[i]);
imageView.setPadding(5, 5, 5, 5);
images.add(imageView);
//定义指示点
ImageView point = new ImageView(this);
point.setBackgroundResource(R.drawable.point_selector);
//设置布局参数
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,-2);//-2,-2
params.leftMargin = 15;
point.setLayoutParams(params);
//添加指示点
pointGroup.addView(point);
if(i==0){
point.setEnabled(true);
}else{
point.setEnabled(false);
}
}
viewpager.setAdapter(new MyPagerAdapter(images));
viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override //页面被滑动
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}
@Override //页面被选择
public void onPageSelected(int position) {
description.setText(imagesDes[position]);
pointGroup.getChildAt(lastPointIndex).setEnabled(false);//上一个变成灰色
pointGroup.getChildAt(position).setEnabled(true);
lastPointIndex = position;
}
@Override //页面状态发生变化,静止——滑动;滑动——静止
public void onPageScrollStateChanged(int state) { }
});
}//onCreate()
}
能够循环
假设现在有 5 张图片,那么对应的坐标是0,1,2,3,4。我们要做的就是实现0123401234……
首先如果每次坐标对5取余,那么仍然得到当前坐标。
在MyPagerAdapter中,首先将图片的数量改为一个很大的值,这里我们改为:
@Override
public int getCount() {
// return images.size();
return Integer.MAX_VALUE;
}
然后把所有position的位置修改为position%images.size()。
public Object instantiateItem(@NonNull ViewGroup container, int position) {
ImageView imageView = images.get(position%images.size());
container.addView(imageView);
return imageView;
}
在MainActivity中,首先为当前页面设置中间位置,这样就能保证开始的时候左右都可以滑动:
//viewpager.setCurrentItem(viewpager.getCurrentItem()+1);
//保证是imageview的整数倍,就能保证第一个开始的还是0.
int item = Integer.MAX_VALUE/2-Integer.MAX_VALUE/2%images.size();
viewpager.setCurrentItem(Integer.MAX_VALUE/2);
接着修改以下内容:
viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
……
@Override //页面被选择
public void onPageSelected(int position) {
int index = position%images.size();
description.setText(imagesDes[index]);
pointGroup.getChildAt(lastPointIndex).setEnabled(false);
pointGroup.getChildAt(index).setEnabled(true);
lastPointIndex = index;
}
});
但是运行时发现,向右滑动没问题,一旦向左滑动就会出现:
在对应出错的位置,加上以下内容:
ViewGroup parent = (ViewGroup)imageView.getParent();
if (parent != null) {
parent.removeView(imageView);
}
public Object instantiateItem(@NonNull ViewGroup container, int position) {
ImageView imageView = images.get(position%images.size());
//container相当于viewpager
ViewGroup parent = (ViewGroup)imageView.getParent();
if (parent != null) {
parent.removeView(imageView);
}
container.addView(imageView);
return imageView;
}
确实可以往左滑动了,但是会出现空白页面,接着做了以下修改问题解决:去掉这里的removeView
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
// container.removeView((View)object);
}
自动循环
我们可以用Handler来实现:
private Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
// super.handleMessage(msg);
if (msg.what == 0) {
int item = viewpager.getCurrentItem() + 1;
viewpager.setCurrentItem(item);
handler.sendEmptyMessageDelayed(0, 3000);
}
}
};
1、需要考虑什么?
要能够自动循环,我们需要注意的问题是,当用户点击某个页面的时候,我们要让循环停止。释放时,继续循环。所以我们可以在用户点击时,删除消息队列里面相应的message,当手指离开时,又继续添加。
首先在每张图片上添加Touch监听,主要涉及到了3个状态,①手指按下ACTION_DOWN,②手指移动ACTION_MOVE,③手指离开ACTION_UP
当手指点一下,马上离开,会触发①③;
当手指在页面滑动时(包括滑离页面),基本每次都会触发①②。
所以按照我们的分析,我们需要在状态时ACTION_DOWN时删除message,在ACTION_UP时添加message。但是这样考虑会有bug,我们继续往下看。
对于Viewpager上的事件监听,这里我们考虑3个状态,④SCROLL_STATE_DRAGGING表示正在拖拽,⑤SCROLL_STATE_SETTLING表示正在调整到最终状态,⑥SCROLL_STATE_IDLE表示调整到了最终状态后的空闲状态。
当我们不去管它,由它自动循环时,只会触发⑤⑥;
当我们拖拽时,会触发④,这时候如果拖拽到中间,没有让任何一个页面完全显示出来,那么不会触发⑤⑥,只有松开手,或者手指拖动到下一个页面完全显示在屏幕时,才会连续触发⑤⑥,接着又会继续触发⑤⑥。
所以总体来看,拖动一次,触发的事件顺序为①②④⑤⑥:
所以这样就看到bug了吧,当我们手指在界面上移动,结束后根本不会触发ACTION_DOWN,那么就不能继续发送消息实现循环。所以我们还得考虑,如果手指在界面上移动了,也就是产生拖拽了,我们可以在调整好了位置之后,也就是在⑥SCROLL_STATE_IDLE继续发送一个消息,实现循环。
为了方便阅读,对代码做了以下方法封装,并且也删除了log.d()的输出。
2、主方法public class MainActivity extends AppCompatActivity implements View.OnTouchListener {
private final String TAG = this.getClass().getSimpleName();
private final int[] imagesIds = {
R.drawable.tazai,
R.drawable.tazaih,
R.drawable.sen,
R.drawable.chuya,
R.drawable.zhitian,
};
private final String[] imagesDes = {
"为野犬,干杯!",
"最年少干部·太宰治",
"港口黑手党的首领",
"污浊了的忧伤之中",
"全世界最好的织田作"
};
private ArrayList images;
private int lastPointIndex = 0;
private boolean isDragging = false;
private Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
// super.handleMessage(msg);
if (msg.what == 0) {
int item = viewpager.getCurrentItem() + 1;
viewpager.setCurrentItem(item);
handler.sendEmptyMessageDelayed(0, 3000);
}
}
};
@BindView(R.id.viewpager)
ViewPager viewpager;
@BindView(R.id.point_group)
LinearLayout pointGroup;
@BindView(R.id.description)
TextView description;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
setTitle("Demo");
init();
initViewpager();
}
………………
}
3、初始化images数据
private void init() {
images = new ArrayList();
for (int i = 0; i < imagesIds.length; i++) {
//添加图片
ImageView imageView = new ImageView(this);
imageView.setBackgroundResource(imagesIds[i]);
imageView.setOnTouchListener(this::onTouch); //为图片设置事件监听
images.add(imageView);
//添加点
ImageView point = new ImageView(this);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, -2);
params.leftMargin = 15;
point.setLayoutParams(params); //为指示点设置参数
point.setBackgroundResource(R.drawable.point_selector);
pointGroup.addView(point);
//默认选择第一个点
if (i == 0) {
point.setEnabled(true);
} else {
point.setEnabled(false);
}
}
}
4、onTouch监听
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: //手指按下
handler.removeMessages(0);
break;
case MotionEvent.ACTION_UP: //手指离开
handler.sendEmptyMessageDelayed(0, 4000);
break;
case MotionEvent.ACTION_MOVE: //手指在界面上移动
break;
}
return true;
}
4、初始化Viewpager
private void initViewpager() {
viewpager.setAdapter(new MyPagerAdapter(images));
int item = Integer.MAX_VALUE / 2 - Integer.MAX_VALUE / 2 % images.size();
viewpager.setCurrentItem(item);
handler.sendEmptyMessageDelayed(0, 3000);
//为viewpager设置监听
viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
//页面被滑动
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
//页面被选择
@Override
public void onPageSelected(int position) {
int index = position % images.size();
description.setText(imagesDes[index]);
pointGroup.getChildAt(lastPointIndex).setEnabled(false);
pointGroup.getChildAt(index).setEnabled(true);
lastPointIndex = index;
}
@Override
public void onPageScrollStateChanged(int state) {
switch (state) {
case ViewPager.SCROLL_STATE_DRAGGING: //拖拽
handler.removeMessages(0);
isDragging = true;
break;
case ViewPager.SCROLL_STATE_SETTLING: //调整
break;
case ViewPager.SCROLL_STATE_IDLE: //空闲
if (isDragging){
handler.removeMessages(0);
handler.sendEmptyMessageDelayed(0, 3000);
}
isDragging = false;
break;
}
}
});
}
点击事件
添加点击事件,由于我们需要获得image的坐标,所以把点击事件放在适配器MyPagerAdapter中,同时增加成员变量:上下文mContext和图片描述imagesDes。
比较简单,不再解释了,直接上最终代码:
public class MyPagerAdapter extends PagerAdapter {
private ArrayList images;
private String[] imagesDes;
private Context mContext;
public MyPagerAdapter(ArrayList images, String[] imagesDes, Context mContext) {
this.images = images;
this.imagesDes = imagesDes;
this.mContext = mContext;
}
@Override
public int getCount() {
return Integer.MAX_VALUE;
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
int index = position%images.size();
ImageView imageView = images.get(index);
ViewGroup parent = (ViewGroup)imageView.getParent();
if (parent != null) {
parent.removeView(imageView);
}
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String text = imagesDes[index];
Toast.makeText(mContext,text,Toast.LENGTH_SHORT).show();
}
});
container.addView(imageView);
return imageView;
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object;
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
// container.removeView((View)object);
}
}
MainActivity中只修改一处即可:
viewpager.setAdapter(new MyPagerAdapter(images,imagesDes,this));
啊,完成!这么久,终于解决了广告条这个小怪兽。
作者:小土boo
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341