转载请注明出处(万分感谢!):
http://blog.youkuaiyun.com/javazejian/article/details/52144889
关联文章:
ViewPager 从入门到带你撸个启动页之ViewPager基础入门(一)
ViewPager 从入门到带你撸个启动页之Fragment+ViewPager(二)
ViewPager 从入门到带你撸个启动页之实战启动页(三)
ViewPager 从入门到带你撸个启动页之实战PageTransformer切换动画特效(四)
一、启动页实现概述
通过前两篇的分享,我们已经基本掌握了ViewPager的基本使用,本篇咱们就来实现一个通用的启动页,先来看看启动页效果:
看来效果还不错,但实际上只用了ViewPager,并没有用到Fragment,因为移动就是3张图片而已,所以PagerAdapter的实现类代码也是相当简单的。唯一比较棘手的是页面移动过程中底部的3个小点也必须跟着移动,这点需要配合ViewPager的滑动监听动态实时小圆点的移动位置。下面我们就来分析它的实现过程吧。
二、启动页实现过程
2.1 实现整体滑动效果
首先我们先来实现整体的滑动效果,也就是添加3张图片作为ViewPager的滑动页面,因此我们的activity_guide.xml如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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/vp_guide"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
</android.support.v4.view.ViewPager>
</RelativeLayout>
接下来我们创建GuideActivity.java:
package com.zejian.commonstartpage;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.ImageView;
import java.util.ArrayList;
import java.util.List;
/**
* Created by zejian
* Time 16/8/7.
* Description:
*/
public class GuideActivity extends Activity{
// ViewPager的数据
private List<ImageView> imageViewList;
ViewPager mViewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 去标题, 需要在setContentView方法之前调用
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_guide);
initView();// 初始化控件
}
/**
* 初始化控件
*/
private void initView() {
mViewPager = (ViewPager) findViewById(R.id.vp_guide);
GuideAdapter adapter = new GuideAdapter();
mViewPager.setAdapter(adapter);
}
/**
* TODO:初始化ViewPager数据 void
*/
private void initData() {
int[] imageResIDs = {R.drawable.guide_1, R.drawable.guide_2,
R.drawable.guide_3};
imageViewList = new ArrayList<>();
ImageView iv;// 图片
for (int i = 0; i < imageResIDs.length; i++) {
iv = new ImageView(this);
iv.setBackgroundResource(imageResIDs[i]);
imageViewList.add(iv);
}
}
class GuideAdapter extends PagerAdapter {
@Override
public int getCount() {
return imageViewList.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
/*
* 删除元素
*/
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
ImageView iv = imageViewList.get(position);
container.addView(iv);// 1. 向ViewPager中添加一个view对象
return iv; // 2. 返回当前添加的view对象
}
}
}
我们简单说一下代码,首先我们通过initData方法去创建我们所需需要的界面,因为我们的界面都是图片,所以这里我们直接new出3个ImageView控件,并设置图片即可,最后我们把其添加到imageViewList数据集合中,到此数据初始化完成:
/**
* 初始化ViewPager数据
*/
private void initData() {
int[] imageResIDs = {R.drawable.guide_1, R.drawable.guide_2,
R.drawable.guide_3};
imageViewList = new ArrayList<>();
ImageView iv;// 图片
for (int i = 0; i < imageResIDs.length; i++) {
iv = new ImageView(this);
iv.setBackgroundResource(imageResIDs[i]);
imageViewList.add(iv);
}
}
接着我们创建GuideAdapter数据适配器继承自PagerAdapter,重写其方法,返回我们所需要的界面,这个前两篇已经说得很清楚了,这里不过多分析了。最后我们把数据适配器GuideAdapter设置给ViewPager即可:
mViewPager = (ViewPager) findViewById(R.id.vp_guide);
GuideAdapter adapter = new GuideAdapter();
mViewPager.setAdapter(adapter);
到此启动页初步实现完成,我们运行一下,看看效果。
不错!预期效果已经开始出现。接下来我们先把最后一页的按钮添加上。
2.2 添加按钮
需要添加按钮的话我们就必须修改activity_guide.xml文件,添加一个按钮如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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/vp_guide"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
</android.support.v4.view.ViewPager>
<Button
android:id="@+id/btn_guide_start_experience"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="60dip"
android:background="@drawable/button_bg"
android:textColor="@drawable/button_textcolor"
android:text="开始体验"
android:textSize="20sp"
android:paddingLeft="20dip"
android:paddingRight="20dip"
android:paddingTop="5dip"
android:paddingBottom="5dip"
android:visibility="gone"
/>
</RelativeLayout>
显然我们添加了一个Button,并将其设置在父布局的底部而且水平居中,注意此时按钮先设置为gone,待需要显示时再设置其可见。然后我们在Activity_Guide.java中添加如下代码:
btnStartExperience = (Button) findViewById(R.id.btn_guide_start_experience)
//设置监听器
btnStartExperience.setOnClickListener(this);
设置完按钮的点击事件后,我们还需要解决一个问题,那就是当需要显示Button时再显示出来,很显然,我们只需在第3页时才需要显示按钮,这时我们就需要借助ViewPager的滑动事件来处理了,因此我们实现ViewPager.OnPageChangeListener接口,并实现其方法如下:
/**
* 当页面正在滚动时 position 当前选中的是哪个页面 positionOffset 比例 positionOffsetPixels 偏移像素
*/
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
}
/**
* 当页面被选中
*/
@Override
public void onPageSelected(int position) {
}
/**
* 当页面滚动状态改变
*/
@Override
public void onPageScrollStateChanged(int state) {
}
简单介绍一下三个方法:
onPageScrolled(int position, float positionOffset,
int positionOffsetPixels)
当页面正在滚动时被回调。
position 表示当前选中的是第几个页面;
positionOffset 是当前页面滑动比例,其值得范围是[0,1),如果页面向右翻动,这个值不断变大,最后在趋近1的情况后突变为0。如果页面向左翻动,这个值不断变小,最后变为0。
positionOffsetPixels 是当前页面滑动像素的偏移距离onPageScrollStateChanged(int state)
当页面滚动状态改变时被回调。有三个值:0(END),1(PRESS) , 2(UP) 。
当用手指滑动翻页时,手指按下去的时候会触发这个方法,state值为1,手指抬起时,如果发生了滑动(即使很小),其值会变为2,最后变为0 。总共执行这个方法三次。当当前页面翻页时,会执行这个方法两次,state值分别为2 , 0 。- onPageSelected(int position)
当页面被选中时回调,position表示被选中的页面,position下标从0开始计算。
很显然,我们应该利用onPageSelected(int position)决定实现我们的Button是否展示,当position为最后一个页面时展示,其他时候都隐藏即可。代码如下:
/**
* 当页面被选中
*/
@Override
public void onPageSelected(int position) {
// 显示体验按钮
if (position == imageViewList.size() - 1) {
// 显示
btnStartExperience.setVisibility(View.VISIBLE);
} else {
btnStartExperience.setVisibility(View.GONE);// 隐藏
}
}
到此按钮也集成完,我们看看整体GuideActivity代码:
package com.zejian.commonstartpage;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
/**
* Created by zejian
* Time 16/8/7.
* Description:
*/
public class GuideActivity extends Activity implements ViewPager.OnPageChangeListener, View.OnClickListener {
// ViewPager的数据
private List<ImageView> imageViewList;
// 开始体验按钮
private Button btnStartExperience;
// ViewPager
ViewPager mViewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 去标题, 需要在setContentView方法之前调用
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_guide);
initView();// 初始化控件
}
/**
* 初始化控件
*/
private void initView() {
mViewPager = (ViewPager) findViewById(R.id.vp_guide);
btnStartExperience = (Button) findViewById(R.id.btn_guide_start_experience);
initData();// 初始化ViewPager数据
GuideAdapter adapter = new GuideAdapter();
mViewPager.setAdapter(adapter);
mViewPager.setOnPageChangeListener(this);// 设置监听器
btnStartExperience.setOnClickListener(this);// 按钮添加监听
}
/**
* TODO:初始化ViewPager数据 void
*/
private void initData() {
int[] imageResIDs = {R.drawable.guide_1, R.drawable.guide_2,
R.drawable.guide_3};
imageViewList = new ArrayList<>();
ImageView iv;// 图片
for (int i = 0; i < imageResIDs.length; i++) {
iv = new ImageView(this);
iv.setBackgroundResource(imageResIDs[i]);
imageViewList.add(iv);
}
}
/**
* 当页面正在滚动时 position 当前选中的是哪个页面 positionOffset 比例 positionOffsetPixels 偏移像素
*/
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
}
/**
* 当页面被选中
*/
@Override
public void onPageSelected(int position) {
// 显示体验按钮
if (position == imageViewList.size() - 1) {
btnStartExperience.setVisibility(View.VISIBLE);// 显示
} else {
btnStartExperience.setVisibility(View.GONE);// 隐藏
}
}
/**
* 当页面滚动状态改变
*/
@Override
public void onPageScrollStateChanged(int state) {
}
/**
* 打开新的界面
*/
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "跳转新界面", Toast.LENGTH_SHORT).show();
}
class GuideAdapter extends PagerAdapter {
@Override
public int getCount() {
return imageViewList.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
/*
* 删除元素
*/
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
ImageView iv = imageViewList.get(position);
container.addView(iv);// 1. 向ViewPager中添加一个view对象
return iv; // 2. 返回当前添加的view对象
}
}
}
我们运行看看其效果:
2.3 添加指示器
终于到最后一步了,也是比较麻烦的一步,为每一个页面添加指示器,而且指示器必须跟随页面的移动而移动。那就先从布局开始吧,我们在activity_guide.xml文件最下方添加如下代码:
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="30dip">
<LinearLayout
android:id="@+id/ll_guide_point_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"></LinearLayout>
<View
android:id="@+id/select_point"
android:layout_width="10px"
android:layout_height="10px"
android:background="@drawable/point_select" />
</RelativeLayout>
我们在布局中添加了一个相对布局,其中包含了一个id为ll_guide_point_group的LinearLayout,这个线性布局主要用于存放指示器列表也就是3个灰色圆点,而另外一个id为select_point的View则是表示一个被选中的红色圆点。接下来我们就先来初始化这些点列表指示器的数据,因为灰色圆点的数量跟启动页面的数量是一致的,所以有多少个页面就有多少个灰色圆点,因此我们只需在生成图片时同时生成灰色圆点即可,每个点的宽高都为10,相隔距离也为10,代码如下:
private void initData() {
int[] imageResIDs = { R.drawable.guide_1, R.drawable.guide_2,
R.drawable.guide_3 };
imageViewList = new ArrayList<ImageView>();
ImageView iv;// 图片
View view;// 点
LayoutParams params; // 参数类
for (int i = 0; i < imageResIDs.length; i++) {
iv = new ImageView(this);
iv.setBackgroundResource(imageResIDs[i]);
imageViewList.add(iv);
// 根据图片的个数, 每循环一次向LinearLayout中添加一个点
view = new View(this);
view.setBackgroundResource(R.drawable.point_normal);
// 设置参数
params = new LayoutParams(10, 10);
if (i != 0) {
params.leftMargin = 10;
}
view.setLayoutParams(params);// 添加参数
llPointGroup.addView(view);
}
}
从代码我们可以看出,根据图片的个数, 每循环一次向LinearLayout(llPointGroup)中添加一个点,最后便可以生成与图片数量相同的灰色圆点。现在我们来运行一下,看看点的效果:
嗯,小圆点已经出现,接下来就是如何移动的问题了。现在我们需要实现的效果是这样的,当我们滑动页面时,红色小圆点也跟着移动,例如当页面滑动到屏幕的一半时,红色小圆点也应处于灰色圆点的中间位置。如下图:
因此,我们去动态计算页面滑动的比例,然后按相同比例去计算红色小圆点需要滑动的距离。比如当第1页往第0页滑动到中间时,此时小红点的滑动距离应该如下计算:
我们假设屏幕宽度为320px,那么当第1页滑动到屏幕中间时,其滑动距离就是160,此时滑动比较就是160/320=0.5,也就是说红色小圆点的滑动比例也是0.5,通过这个滑动比例我们就可以计算出两个点间的滑动距离,我们可以通过以下方式计算出小红点要滑动的距离:
pWidth=llPointGroup.getChildAt(1).getLeft()-llPointGroup.getChildAt(0).getLeft()
最后乘以滑动比例就是小红点的滑动距离了。当然这只是其中一次滑动的完整计算过程,页面滑动过程中的小红点变化的计算方式也是相同(因为滑动过程中会多次调用onPageScrolled方法后面会说明),现在剩下的问题是我们如何知道页面滑动过程呢?还记得前面我们ViewPager设置的OnPageChangeListener监听器吧?其中有个回调方法onPageScrolled(int position, float positionOffset, int positionOffsetPixels)的参数positionOffset不就是页面的滑动比例吗?确实如此,因此我们可以利用这个滑动比例来动态计算小红点的滑动距离,这样也就可以达到页面滑动的过程中小红点也跟随移动的效果。下面我们给出实现代码:
/**
* 当页面正在滚动时 position 当前选中的是哪个页面 positionOffset 比例 positionOffsetPixels 偏移像素
*/
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
//获取两个点间的距离,获取一次即可
if(pWidth==0) {
pWidth = llPointGroup.getChildAt(1).getLeft()
- llPointGroup.getChildAt(0).getLeft();
}
// 获取点要移动的距离
int leftMargin = (int) (pWidth * (position + positionOffset));
// 给红点设置参数
RelativeLayout.LayoutParams params = (android.widget.RelativeLayout.LayoutParams) mSelectPointView
.getLayoutParams();
params.leftMargin = leftMargin;
mSelectPointView.setLayoutParams(params);
}
这里我们再来简单分析一下计算移动距离的代码:
// 获取点要移动的距离
int leftMargin = (int) (pWidth * (position + positionOffset));
由于position表示当前页面的下标,取值范围在[0,2],而positionOffset则表示当前界面的滑动比例,取值范围在[0,1),当页面向右滑动,positionOffset值不断变大,最后在趋近1的情况后突变为0。如果页面向左滑动,这个值不断变小,最后变为0,当positionOffset突变为0时则说明页面已处于展示位置。因此当我们把当前页面往左滑动时,当前position=0,而positionOffset则在不断的增大,我们过Log来观察一下
当第0页滑动到左侧,而第1页正好在中间展示时,position=1,而positionOffset=0;
因此我们可以通过positionOffset的值来计算小红点的滑动距离,当当前页面往左滑动时,positionOffset不断增大,此时由于position=0;所以pWidth * (position + positionOffset)也在不断增大,计算出小红点的移动距离leftMargin后,我们只需动态设置其params.leftMargin参数即可,这也就达到了小红点跟随移动的目的(onPageScrolled函数在移动中多次被触发),最终position=1,positionOffset=0,小红点也刚好落在了第2个灰色小圆点上。往后第3个点原理也是一样的,向右移动也依次类推即可。。
最后我们给出GuideActivity的代码:
package com.zejian.commonstartpage;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.RelativeLayout;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
/**
* Created by zejian
* Time 16/8/7.
* Description:
*/
public class GuideActivity extends Activity implements ViewPager.OnPageChangeListener, View.OnClickListener {
// ViewPager的数据
private List<ImageView> imageViewList;
// 点的组
private LinearLayout llPointGroup;
// 选中的点view对象
private View mSelectPointView;
// 开始体验按钮
private Button btnStartExperience;
// ViewPager
ViewPager mViewPager;
// 点之间的宽度
private int pWidth;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 去标题, 需要在setContentView方法之前调用
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_guide);
initView();// 初始化控件
}
/**
* 初始化控件
*/
private void initView() {
mViewPager = (ViewPager) findViewById(R.id.vp_guide);
btnStartExperience = (Button) findViewById(R.id.btn_guide_start_experience);
llPointGroup = (LinearLayout) findViewById(R.id.ll_guide_point_group);
mSelectPointView = findViewById(R.id.select_point);
initData();// 初始化ViewPager数据
GuideAdapter adapter = new GuideAdapter();
mViewPager.setAdapter(adapter);
mViewPager.setOnPageChangeListener(this);// 设置监听器
btnStartExperience.setOnClickListener(this);// 按钮添加监听
}
/**
* TODO:初始化ViewPager数据 void
*/
private void initData() {
int[] imageResIDs = {R.drawable.guide_1, R.drawable.guide_2,
R.drawable.guide_3};
imageViewList = new ArrayList<>();
ImageView iv;// 图片
View view;// 点
LayoutParams params; // 参数类
for (int i = 0; i < imageResIDs.length; i++) {
iv = new ImageView(this);
iv.setBackgroundResource(imageResIDs[i]);
imageViewList.add(iv);
// 根据图片的个数, 每循环一次向LinearLayout中添加一个点
view = new View(this);
view.setBackgroundResource(R.drawable.point_normal);
// 设置参数
params = new LayoutParams(10, 10);
if (i != 0) {
params.leftMargin = 10;
}
view.setLayoutParams(params);// 添加参数
llPointGroup.addView(view);
}
}
/**
* 当页面正在滚动时 position 当前选中的是哪个页面 positionOffset 比例 positionOffsetPixels 偏移像素
*/
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
Log.e("zejian","positionOffset:-->"+positionOffset);
Log.e("zejian","position:-->"+position);
//获取两个点间的距离,获取一次即可
if(pWidth==0) {
pWidth = llPointGroup.getChildAt(1).getLeft()
- llPointGroup.getChildAt(0).getLeft();
}
// 获取点要移动的距离
int leftMargin = (int) (pWidth * (position + positionOffset));
// 给红点设置参数
RelativeLayout.LayoutParams params = (android.widget.RelativeLayout.LayoutParams) mSelectPointView
.getLayoutParams();
params.leftMargin = leftMargin;
mSelectPointView.setLayoutParams(params);
}
/**
* 当页面被选中
*/
@Override
public void onPageSelected(int position) {
// 显示体验按钮
if (position == imageViewList.size() - 1) {
btnStartExperience.setVisibility(View.VISIBLE);// 显示
} else {
btnStartExperience.setVisibility(View.GONE);// 隐藏
}
}
/**
* 当页面滚动状态改变
*/
@Override
public void onPageScrollStateChanged(int state) {
}
/**
* 打开新的界面
*/
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "跳转新界面", Toast.LENGTH_SHORT).show();
}
class GuideAdapter extends PagerAdapter {
@Override
public int getCount() {
return imageViewList.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
/*
* 删除元素
*/
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
ImageView iv = imageViewList.get(position);
container.addView(iv);// 1. 向ViewPager中添加一个view对象
return iv; // 2. 返回当前添加的view对象
}
}
}
最近效果:
到此,启动页已全部完成,告本篇一段落。源码下载:
启动页(commonstartpage项目)源码GitHub下载地址
ViewPager 从入门到带你撸个启动页之ViewPager基础入门(一)
ViewPager 从入门到带你撸个启动页之Fragment+ViewPager(二)