这是自定义 View 系列的第二篇博客, 我们继续来学习关于自定义 View 的知识.
今天我们来实现一下广告条案例.
我们要实现的是这样的一个效果.
要想实现这样的效果, 我们可以借助 ViewPager 控件, 然后加上自定义的一些控件即可完成. 那么现在就开始吧.
新建一个 Android 项目.
修改 activity_main.xml 文件.
- <?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"
- xmlns:tools="http://schemas.android.com/tools"
- Android:layout_width="match_parent"
- Android:layout_height="match_parent"
- tools:context="com.itcast.test0429.MainActivity">
- <Android.support.v4.view.ViewPager
- Android:id="@+id/viewpager"
- Android:layout_width="match_parent"
- Android:layout_height="180dp"/>
- <LinearLayout
- Android:layout_width="match_parent"
- Android:layout_height="wrap_content"
- Android:layout_alignBottom="@id/viewpager"
- Android:background="#44000000"
- Android:orientation="vertical"
- Android:padding="5dp">
- <TextView
- Android:id="@+id/tv_title"
- Android:layout_width="match_parent"
- Android:layout_height="wrap_content"
- Android:gravity="center_horizontal"
- Android:padding="3dp"
- Android:text="复仇者联盟 4"
- Android:textColor="#ffffff" />
- <LinearLayout
- Android:id="@+id/ll_point_group"
- Android:layout_width="match_parent"
- Android:layout_height="wrap_content"
- Android:gravity="center_horizontal"
- Android:orientation="horizontal">
- </LinearLayout>
- </LinearLayout>
- </RelativeLayout>
然后修改 MainActivity 的代码.
- package com.itcast.test0429;
- import Android.os.Bundle;
- import Android.support.v4.view.PagerAdapter;
- import Android.support.v4.view.ViewPager;
- import Android.support.v7.App.AppCompatActivity;
- import Android.util.Log;
- import Android.view.View;
- import Android.view.ViewGroup;
- import Android.widget.ImageView;
- import Android.widget.LinearLayout;
- import Android.widget.TextView;
- import java.util.ArrayList;
- import butterknife.BindInt;
- import butterknife.BindView;
- import butterknife.ButterKnife;
- public class MainActivity extends AppCompatActivity {
- private static final String TAG = MainActivity.class.getSimpleName();
- @BindView(R.id.viewpager)
- ViewPager viewpager;
- @BindView(R.id.tv_title)
- TextView tvTitle;
- @BindView(R.id.ll_point_group)
- LinearLayout llPointGroup;
- private ArrayList<ImageView> imageViews;
- // 图片资源 ID
- private final int[] imageIds = {
- R.drawable.b1,
- R.drawable.b2,
- R.drawable.b3
- };
- // 图片标题集合
- private final String[] imageDescriptions = {
- "标题一",
- "标题二",
- "标题三"
- };
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- ButterKnife.bind(this);
- imageViews = new ArrayList<>();
- for(int i = 0;i <imageIds.length;i++){
- ImageView imageView = new ImageView(this);
- imageView.setBackgroundResource(imageIds[i]);
- // 添加到集合中
- imageViews.add(imageView);
- }
- // 设置适配器
- viewpager.setAdapter(new MyAdapter());
- }
- class MyAdapter extends PagerAdapter{
- /**
- * 得到图片的总数
- * @return
- */
- @Override
- public int getCount() {
- return imageViews.size();
- }
- /**
- * 相当于 getView 方法
- * @param container ViewPager 自身
- * @param position 当前实例化页面的位置
- * @return
- */
- @Override
- public Object instantiateItem(ViewGroup container, int position) {
- ImageView imageView = imageViews.get(position);
- container.addView(imageView);// 添加到 ViewPager 中
- Log.e(TAG,"instantiateItem==" + position + ",---imageView==" + imageView);
- return imageView;
- }
- /**
- * 比较 view 和 object 是否同一个实例
- * @param view 页面
- * @param object instantiateItem 方法返回的结果
- * @return
- */
- @Override
- public boolean isViewFromObject(View view, Object object) {
- return view == object;
- }
- /**
- * 释放资源
- * @param container ViewPager
- * @param position 要释放的位置
- * @param object 要释放的页面
- */
- @Override
- public void destroyItem(ViewGroup container, int position, Object object) {
- Log.e(TAG,"destroyItem==" + position + ",---object==" + object);
- container.removeView((View) object);
- }
- }
- }
需要注意的就是 PageAdapter 的使用, 每个方法我都写了注释, 大家应该能理解吧. ViewPager 在运行之后总共会初始化两个页面, 最多初始化三个, 随着页面的增多, ViewPager 会自动销毁前面的页面进而提供给后面的页面使用, 这是 ViewPage 的内容优化, 我们可以来验证一下, 我在初始化和销毁的方法中都打印了日志. 现在, 我们来运行项目.
我们看看日志情况.
只打印了两次初始化信息, 说明 ViewPager 只初始化了两个页面.
我们向左滑动 ViewPager 来切换页面, 此时观察日志信息.
会发现在创建了三个实例后, 第一个页面的实例就被销毁了, 这就证实了刚才的结论.
这样我们第一阶段的编码就完成了, 接下来我们实现添加指示点, 并根据页面改变设置文本.
而指示点有多种实现方式, 可以通过图片来显示, 也可以自己绘制指示点显示, 我这种用的是第二种方式. 贴出 MainActivity 的代码.
- package com.itcast.test0429;
- import Android.os.Bundle;
- import Android.support.v4.view.PagerAdapter;
- import Android.support.v4.view.ViewPager;
- import Android.support.v7.App.AppCompatActivity;
- import Android.util.Log;
- import Android.view.View;
- import Android.view.ViewGroup;
- import Android.widget.ImageView;
- import Android.widget.LinearLayout;
- import Android.widget.TextView;
- import java.util.ArrayList;
- import butterknife.BindView;
- import butterknife.ButterKnife;
- import butterknife.OnClick;
- import butterknife.OnPageChange;
- public class MainActivity extends AppCompatActivity {
- private static final String TAG = MainActivity.class.getSimpleName();
- @BindView(R.id.viewpager)
- ViewPager viewpager;
- @BindView(R.id.tv_title)
- TextView tvTitle;
- @BindView(R.id.ll_point_group)
- LinearLayout llPointGroup;
- private ArrayList<ImageView> imageViews;
- // 图片资源 ID
- private final int[] imageIds = {
- R.drawable.b1,
- R.drawable.b2,
- R.drawable.b3,
- R.drawable.b1,
- R.drawable.b2,
- R.drawable.b3
- };
- /**
- * 上一次高亮显示的位置
- */
- private int prePosition = 0;
- // 图片标题集合
- private final String[] imageDescriptions = {
- "标题一",
- "标题二",
- "标题三",
- "标题四",
- "标题五",
- "标题六"
- };
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- ButterKnife.bind(this);
- imageViews = new ArrayList<>();
- for (int i = 0; i <imageIds.length; i++) {
- ImageView imageView = new ImageView(this);
- imageView.setBackgroundResource(imageIds[i]);
- // 添加到集合中
- imageViews.add(imageView);
- // 添加点
- ImageView point = new ImageView(this);
- point.setBackgroundResource(R.drawable.point_selector);
- LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(8, 8);
- if (i == 0) {
- point.setEnabled(true);// 显示红色
- } else {
- point.setEnabled(false);// 显示灰色
- params.leftMargin = 8;
- }
- point.setLayoutParams(params);
- llPointGroup.addView(point);
- }
- // 设置适配器
- viewpager.setAdapter(new MyAdapter());
- tvTitle.setText(imageDescriptions[prePosition]);
- // 设置监听 ViewPager 页面的改变
- viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
- /**
- * 当页面滚动的时候回调此方法
- * @param position 当前页面的位置
- * @param positionOffset 滑动页面的百分比
- * @param positionOffsetPixels 在屏幕上滑动的像素
- */
- @Override
- public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
- }
- /**
- * 当某个页面被选中的时候回调此方法
- * @param position 被选中页面的位置
- */
- @Override
- public void onPageSelected(int position) {
- // 设置对应页面的文本信息
- tvTitle.setText(imageDescriptions[position]);
- // 把上一个高亮的设置为默认 - 灰色
- llPointGroup.getChildAt(prePosition).setEnabled(false);
- // 当前设置为高亮 - 红色
- llPointGroup.getChildAt(position).setEnabled(true);
- prePosition = position;
- }
- /**
- * 当页面滚动状态变化的时候回调此方法
- * 静止 -》滑动
- * 滑动 --》静止
- * 静止 ---》拖拽
- * @param state
- */
- @Override
- public void onPageScrollStateChanged(int state) {
- }
- });
- }
- class MyAdapter extends PagerAdapter {
- /**
- * 得到图片的总数
- *
- * @return
- */
- @Override
- public int getCount() {
- return imageViews.size();
- }
- /**
- * 相当于 getView 方法
- *
- * @param container ViewPager 自身
- * @param position 当前实例化页面的位置
- * @return
- */
- @Override
- public Object instantiateItem(ViewGroup container, int position) {
- ImageView imageView = imageViews.get(position);
- container.addView(imageView);// 添加到 ViewPager 中
- Log.e(TAG, "instantiateItem==" + position + ",---imageView==" + imageView);
- return imageView;
- }
- /**
- * 比较 view 和 object 是否同一个实例
- *
- * @param view 页面
- * @param object instantiateItem 方法返回的结果
- * @return
- */
- @Override
- public boolean isViewFromObject(View view, Object object) {
- return view == object;
- }
- /**
- * 释放资源
- *
- * @param container ViewPager
- * @param position 要释放的位置
- * @param object 要释放的页面
- */
- @Override
- public void destroyItem(ViewGroup container, int position, Object object) {
- Log.e(TAG, "destroyItem==" + position + ",---object==" + object);
- container.removeView((View) object);
- }
- }
- }
指示点我是用 xml 文件绘制的, point_selecotr.xml 文件代码如下.
- <?xml version="1.0" encoding="utf-8"?>
- <selector xmlns:Android="http://schemas.android.com/apk/res/android">
- <item Android:state_enabled="false" Android:drawable="@drawable/point_normal"/>
- <item Android:state_enabled="true" Android:drawable="@drawable/point_press"/>
- </selector>
point_normal.xml 文件代码如下.
- <?xml version="1.0" encoding="utf-8"?>
- <shape xmlns:Android="http://schemas.android.com/apk/res/android"
- Android:shape="oval">
- <size
- Android:width="8dp"
- Android:height="8dp" />
- <solid Android:color="#44000000" />
- </shape>
point_press.xml 文件代码如下.
- <?xml version="1.0" encoding="utf-8"?>
- <shape xmlns:Android="http://schemas.android.com/apk/res/android"
- Android:shape="oval">
- <size
- Android:width="8dp"
- Android:height="8dp" />
- <solid Android:color="#ff0000" />
- </shape>
我们运行项目, 预览效果.
这个时候, 指示点和文本标题的内容都随着我们的滑动而改变, 这样, 我们的目的就达到了.
我们来实现第三阶段的需求, 支持左右无限滑动.
怎么才能实现这个需求呢? 滑动的页面数量是由适配器的 getCount 方法决定的, 所以, 我们在 getCount 方法里直接返回 Integer.MAX_VALUE, 这是 int 的最大值, 这个数量已经非常庞大了, 可以说近似于无限滑动, 但是设置如此大的数量, 而我们的数据又没有这么多, 那么在滑动页面的时候肯定会产生索引越界的问题, 所以, 为了避免这样的问题产生, 我们就必须把有页面位置的地方全部取模处理, 让其保持在我们的有限数据范围内, 这样就能够实现我们的需求了. 那么我们来修改 MainActivity 的代码.
- package com.itcast.test0429;
- import Android.os.Bundle;
- import Android.support.v4.view.PagerAdapter;
- import Android.support.v4.view.ViewPager;
- import Android.support.v7.App.AppCompatActivity;
- import Android.util.Log;
- import Android.view.View;
- import Android.view.ViewGroup;
- import Android.widget.ImageView;
- import Android.widget.LinearLayout;
- import Android.widget.TextView;
- import java.util.ArrayList;
- import butterknife.BindView;
- import butterknife.ButterKnife;
- import butterknife.OnClick;
- import butterknife.OnPageChange;
- public class MainActivity extends AppCompatActivity {
- private static final String TAG = MainActivity.class.getSimpleName();
- @BindView(R.id.viewpager)
- ViewPager viewpager;
- @BindView(R.id.tv_title)
- TextView tvTitle;
- @BindView(R.id.ll_point_group)
- LinearLayout llPointGroup;
- private ArrayList<ImageView> imageViews;
- // 图片资源 ID
- private final int[] imageIds = {
- R.drawable.b1,
- R.drawable.b2,
- R.drawable.b3,
- R.drawable.b1,
- R.drawable.b2,
- R.drawable.b3
- };
- /**
- * 上一次高亮显示的位置
- */
- private int prePosition = 0;
- // 图片标题集合
- private final String[] imageDescriptions = {
- "标题一",
- "标题二",
- "标题三",
- "标题四",
- "标题五",
- "标题六"
- };
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- ButterKnife.bind(this);
- imageViews = new ArrayList<>();
- for (int i = 0; i < imageIds.length; i++) {
- ImageView imageView = new ImageView(this);
- imageView.setBackgroundResource(imageIds[i]);
- // 添加到集合中
- imageViews.add(imageView);
- // 添加点
- ImageView point = new ImageView(this);
- point.setBackgroundResource(R.drawable.point_selector);
- LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(8, 8);
- if (i == 0) {
- point.setEnabled(true);// 显示红色
- } else {
- point.setEnabled(false);// 显示灰色
- params.leftMargin = 8;
- }
- point.setLayoutParams(params);
- llPointGroup.addView(point);
- }
- // 设置适配器
- viewpager.setAdapter(new MyAdapter());
- tvTitle.setText(imageDescriptions[prePosition]);
- // 设置监听 ViewPager 页面的改变
- viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
- /**
- * 当页面滚动的时候回调此方法
- * @param position 当前页面的位置
- * @param positionOffset 滑动页面的百分比
- * @param positionOffsetPixels 在屏幕上滑动的像素
- */
- @Override
- public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
- }
- /**
- * 当某个页面被选中的时候回调此方法
- * @param position 被选中页面的位置
- */
- @Override
- public void onPageSelected(int position) {
- int realPosition = position % imageViews.size();
- // 设置对应页面的文本信息
- tvTitle.setText(imageDescriptions[realPosition]);
- // 把上一个高亮的设置为默认 - 灰色
- llPointGroup.getChildAt(prePosition).setEnabled(false);
- // 当前设置为高亮 - 红色
- llPointGroup.getChildAt(realPosition).setEnabled(true);
- prePosition = realPosition;
- }
- /**
- * 当页面滚动状态变化的时候回调此方法
- * 静止 -》滑动
- * 滑动 --》静止
- * 静止 ---》拖拽
- * @param state
- */
- @Override
- public void onPageScrollStateChanged(int state) {
- }
- });
- }
- class MyAdapter extends PagerAdapter {
- /**
- * 得到图片的总数
- *
- * @return
- */
- @Override
- public int getCount() {
- return Integer.MAX_VALUE;
- }
- /**
- * 相当于 getView 方法
- *
- * @param container ViewPager 自身
- * @param position 当前实例化页面的位置
- * @return
- */
- @Override
- public Object instantiateItem(ViewGroup container, int position) {
- int realPosition = position % imageViews.size();
- ImageView imageView = imageViews.get(realPosition);
- container.addView(imageView);// 添加到 ViewPager 中
- Log.e(TAG, "instantiateItem==" + position + ",---imageView==" + imageView);
- return imageView;
- }
- /**
- * 比较 view 和 object 是否同一个实例
- *
- * @param view 页面
- * @param object instantiateItem 方法返回的结果
- * @return
- */
- @Override
- public boolean isViewFromObject(View view, Object object) {
- return view == object;
- }
- /**
- * 释放资源
- *
- * @param container ViewPager
- * @param position 要释放的位置
- * @param object 要释放的页面
- */
- @Override
- public void destroyItem(ViewGroup container, int position, Object object) {
- Log.e(TAG, "destroyItem==" + position + ",---object==" + object);
- container.removeView((View) object);
- }
- }
- }
这样就可以了, 我们来运行一下项目.
乍一看, 好像是没什么问题了, 但是, 这个程序是有 bug 的, 只是这样我们没有测试出来, 我再操作一遍.
发现问题了没有, 我在最开始进入程序的时候, 右滑是不是滑不动? 因为 ViewPager 默认从 0 开始, 所以左边已经没有其它页面了, 故你无法右滑, 那怎么解决这个问题呢? 找到了问题的原因, 那就可以有解决办法, 既然左边没有页面, 那就让它有页面不就得了? 我们把第一张图定位到中间位置, 那么它的左边和右边就都会拥有数量庞大的页面. 虽然页面很多, 但也不是滑不完的, 假如有一个用户, 他就是闲得慌, 他就拼命地滑, 结果, 把左边或者右边的所有页面都滑完了, 这种情况我们只能说这个人他真的是闲得慌了, 总之, 按照正常情况, 如此多的页面时足够用户滑动了.
原理我们知道了, 如何通过编码实现呢? 很简单, 只需要 ViewPager 设置适配器之后加上如下代码即可.
- // 设置中间位置
- int item = Integer.MAX_VALUE / 2 - Integer.MAX_VALUE / 2 % imageViews.size();// 要保证 imageViews 的整数倍
- viewpager.setCurrentItem(item);
现在运行项目, 预览效果.
这样问题就解决了. 整个案例也就结束了, 这只是 ViewPager 使用的一小部分, 这个程序还可以加上很多的功能, 比如自动播放, 然后点击跳转等等, 由于篇幅有限, 我就不一一实现了, 感兴趣的可以自己尝试着写一写.
源码已上传至 GitHub https://github.com/blizzawang/ViewPagerDemo.git
来源: http://www.bubuko.com/infodetail-3170670.html