大家好 我是akira 前面学了那么多的零碎 你是不是觉得有点无聊了呢 不要紧 今天咱来点有意思的
自己动作搞一个软件的引导界面 什么是引导界面 我们看下以下几个图你就知道了
当然 以上图我只是反编译了一下某app的 我们通常在用的时候 都发现有这么些东西 有些引导页面没有下面的点 有些有
最后进行点击进入home 注意 这里在手机第一次安装app的时候有引导 在下一次进入的时候 是直接跳到home中去的
好了 我们想知道的就是这么多
咱来分析一下
这里我将引入一个新的控件叫做viewPager 因为通常情况下做引导界面有两种方式 过去的andriod3.0之前没有viewPager时可能更麻烦些
1 是使用我们即将要使用的viewPager来做 2是使用一个开源的框架叫做ViewFlow 这个有兴趣的可以搜下
OK 我们在分析一点就是 无论这几个图怎么动始终有三个东西1 背景图 2 那几个小圆点 当然 这个app偷了个懒 实际上大多数app使用的是动态加载
shape的方法 第三个就是两个状态的按钮 没错就这么多 分析了这么多之后 我们来动手写布局 写代码
先来一个简单的 也就是 我们只要四张图 其他的都可以不用管
布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<android.support.v4.view.ViewPager
android:id="@+id/vp"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
这里就是相对布局套用一个viewPager 这里注意下 问了兼容性 咱都采用v4的包 而既然是控件那么肯定有宽高 宽高均为填充父窗体
我们看下主要的代码
public class GideActivty extends Activity {
private ViewPager pager;
private List<ImageView> images;
private ImageAdater adater;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
//设置无标题和全屏
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_gide);
findview();
initData();
}
/**
* 初始化数据
*/
private void initData() {
images = new ArrayList<ImageView>();
ImageView im1 = new ImageView(this);
im1.setBackgroundResource(R.drawable.welcome_intro_0);
ImageView im2 = new ImageView(this);
im2.setBackgroundResource(R.drawable.welcome_intro_1);
ImageView im3 = new ImageView(this);
im3.setBackgroundResource(R.drawable.welcome_intro_2);
ImageView im4 = new ImageView(this);
im4.setBackgroundResource(R.drawable.welcome_intro_3);
images.add(im1);
images.add(im2);
images.add(im3);
images.add(im4);
adater = new ImageAdater(images);
pager.setAdapter(adater);
}
private void findview() {
pager = (ViewPager) findViewById(R.id.vp);
}
private class ImageAdater extends PagerAdapter{
private List<ImageView> ims;
public ImageAdater(List<ImageView> images) {
this.ims = images;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager)container).removeView(ims.get(position));
// super.destroyItem(container, position, object);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
((ViewPager)container).addView(ims.get(position), 0);
return ims.get(position);
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return ims.size();
}
@Override
public boolean isViewFromObject(View arg0, Object arg1) {
// TODO Auto-generated method stub
return arg0==arg1;
}
}
}
这里代码比较长 我来具体说明一下 首先你肯定要拿到控件 findview
接下来先不管别的 我们要知道一点 既然是ViewPager 那么就会有适配器 问题来了 什么是适配器
这个就好比比如我现在手里有一个港版的iphone6 但我知道
港版的电器充电器在大陆是无法适应的 因此你需要买一个旧标的插线板或者是转换头
这种东西就是一种适配器 现在你只要看成是一个加工车间就好了 是viewPager的加工车间
ok 这里我们有了适配器 adapter 接下来 有一点就是 viewpager的自定义adapter最好都是继承PagerAdapter的
很显然 PagerAdapter是一个抽象类 那么好 我们实现它的方法
eclipse有一点不智能就是 实际上起作用的并不是 getCount 和 isViewFromObject 而是
前两个 一个是添加 一个是销毁 很显然 我们添加一个 再滑动后肯定要销毁 那么OK 知道了这点
我们再看下adapter的代码解析
private class ImageAdater extends PagerAdapter{
private List<ImageView> ims;
public ImageAdater(List<ImageView> images) {
this.ims = images;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager)container).removeView(ims.get(position));
// super.destroyItem(container, position, object);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
((ViewPager)container).addView(ims.get(position), 0);
return ims.get(position);
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return ims.size();
}
@Override
public boolean isViewFromObject(View arg0, Object arg1) {
// TODO Auto-generated method stub
return arg0==arg1;
}
}
这里我是用一个私有内部类 当然你也可以更规范的做法 建一个包 命为adapter 然后把代码放进去 导包完成 这都OK的
我想说的不是这个 我们现在的目的是动态的去加载图 那么怎么加载呢利用原有的经验是在xml上设置background 但是这样在动态操作的时候效果是不好的
而且 andriod本身是利用反射机制去调用图的 所以更好的做法是代码
因为是图 所以是ImageView(以下简称iv) 那么我们观察iv的构造不难发现 里面是有个上下文的 而我们在设置的时候 跟背景异曲同工
有个方法是 setBackgroundResource 里面传的就是你的图 知道这个 你不难理解这个方法的意义
/**
* 初始化数据
*/
private void initData() {
images = new ArrayList<ImageView>();
ImageView im1 = new ImageView(this);
im1.setBackgroundResource(R.drawable.welcome_intro_0);
ImageView im2 = new ImageView(this);
im2.setBackgroundResource(R.drawable.welcome_intro_1);
ImageView im3 = new ImageView(this);
im3.setBackgroundResource(R.drawable.welcome_intro_2);
ImageView im4 = new ImageView(this);
im4.setBackgroundResource(R.drawable.welcome_intro_3);
images.add(im1);
images.add(im2);
images.add(im3);
images.add(im4);
adater = new ImageAdater(images);
pager.setAdapter(adater);
}
接下来 我们要思考 我们如何让适配器知道这里有几张图 并且显示呢
最简单的办法无非是构造传参 这个太熟悉了 于是就有了传入list的做法
而既然有了值 那么getCount 就不难理解了 而isViewObject 这个要怎么理解呢
我认为 这就好比是1图和2图在滑动的时候 系统会做一个判断 因为你不一定全部滑到2 也不一定全部走出1 所以系统要知道这是不是同一个view在
我们这里可以理解为同一个图
于是乎 就有了下面更精确的说法
功能:该函数用来判断instantiateItem(ViewGroup, int)函数所返回来的Key与一个页面视图是否是代表的同一个视图(即它俩是否是对应的,对应的表示同一个View)
返回值:如果对应的是同一个View,返回True,否则返回False。
绿字摘自http://blog.youkuaiyun.com/harvic880925/article/details/38487149好的 关键的两个方法来了 一个是初始化 一个是销毁 这个它没有生成 但你可以实现其未实现方法中找到
快捷键
alt shift +s 找到OrriadeXXX选项 选择方法 我们知道 我们传进来的是list 而list拿对象最方便的是依靠position
于是乎 我们利用viewGroup的addView方法 就可以完成
((ViewPager)container).addView(ims.get(position), 0);
return ims.get(position);
同理 销毁
((ViewPager)container).removeView(ims.get(position));
对于addView和removeView 现在你有个大概影响 以后我会再讲到
而viewGroup是方法里传的(你可以理解为装view的架子) 我们要用肯定要强转为ViewPager
下面 运行一下 效果图如下
下面就是需求中的第二个问题 如何找到按钮控件
我们发现由于是图 所以很难找到按钮 最好的方法是将按钮分离开来并设置监听
在viewPager中有onPagerChangeListener 好 我们接下来完善代码
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<android.support.v4.view.ViewPager
android:id="@+id/vp"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<ImageButton
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="30dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/btnnormal"
android:contentDescription="@null"
/>
</RelativeLayout>
这里我们发现就加了一个按钮 接下来 我要对这个按钮进行操作 我们加一个id
android:id="@+id/ib"
OK 再次回到原来的引导act 这次我们要做的就是判断当前的position是否为最后一个并且改变ib的图标 设置监听
代码如下
public class GideActivty extends Activity implements OnPageChangeListener {
private ViewPager pager;
private List<ImageView> images;
private ImageAdater adater;
private ImageButton ib;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
//设置无标题和全屏
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_gide);
findview();
initData();
setListener();
}
private void setListener() {
pager.setOnPageChangeListener(this);
ib.setClickable(false);
}
/**
* 初始化数据
*/
private void initData() {
images = new ArrayList<ImageView>();
ImageView im1 = new ImageView(this);
im1.setBackgroundResource(R.drawable.welcome_intro_0);
ImageView im2 = new ImageView(this);
im2.setBackgroundResource(R.drawable.welcome_intro_1);
ImageView im3 = new ImageView(this);
im3.setBackgroundResource(R.drawable.welcome_intro_2);
ImageView im4 = new ImageView(this);
im4.setBackgroundResource(R.drawable.welcome_intro_3);
images.add(im1);
images.add(im2);
images.add(im3);
images.add(im4);
adater = new ImageAdater(images);
pager.setAdapter(adater);
}
private void findview() {
pager = (ViewPager) findViewById(R.id.vp);
ib = (ImageButton) findViewById(R.id.ib);
}
private class ImageAdater extends PagerAdapter{
private List<ImageView> ims;
public ImageAdater(List<ImageView> images) {
this.ims = images;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager)container).removeView(ims.get(position));
// super.destroyItem(container, position, object);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
((ViewPager)container).addView(ims.get(position), 0);
return ims.get(position);
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return ims.size();
}
@Override
public boolean isViewFromObject(View arg0, Object arg1) {
// TODO Auto-generated method stub
return arg0==arg1;
}
}
@Override
public void onPageScrollStateChanged(int arg0) {
// TODO Auto-generated method stub
}
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
// TODO Auto-generated method stub
}
@Override
public void onPageSelected(int position) {
if(position==3){
//这里监听最后一个页面
ib.setBackgroundResource(R.drawable.btnen);
ib.setClickable(true);
ib.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(GideActivty.this, MainActivity.class));
}
});
}
}
}
这里各位不要晕 依然还是看修改的代码 我们知道 我们是加了一个按钮的 因为原来的背景我用ps软件全部抠图抠掉了
现在 我们找到控件 依然是findview 然后设置监听
ib.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(GideActivty.this, MainActivity.class));
}
});
这是采用了另一种设置监听的方式 俗称传匿名内部类 或者接口
然后依然是实现方法 不通的是 我们要知道如何滑动到最后一个 因为0 1 2 3 所以最后的position肯定为3
就有了
@Override
public void onPageSelected(int position) {
if(position==3){
//这里监听最后一个页面
ib.setBackgroundResource(R.drawable.btnen);
ib.setClickable(true);
ib.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(GideActivty.this, MainActivity.class));
}
});
}
}
我们前面不让其点击生效使用了setClickable的false或者true
然后去动态的改变图
private void setListener() {
pager.setOnPageChangeListener(this);
ib.setClickable(false);
}
这里面方法的setOnpagerChangListener是我们使用viewpager的时候最常用的一个监听
但是这里面有个问题 就是 我们在滑动到最后一个界面的时候 再划回来 由于设置背景 所以改不回来了 因此 还要加上逻辑判断
@Override
public void onPageSelected(int position) {
if(position==3){
//这里监听最后一个页面
ib.setBackgroundResource(R.drawable.btnen);
ib.setClickable(true);
ib.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(GideActivty.this, MainActivity.class));
}
});
}else{
ib.setClickable(false);
ib.setBackgroundResource(R.drawable.btnnormal);
}
}
剩下要做的就是注释掉setListener的的setClickXXX方法即可
效果图如下
最后一个需求 是要第一次进入应用的时候 还记得首选项么 没错 我们就用首选项存数据
代码如下
我们这个时候 需要建立一个splash界面
再写一个活动
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg"
tools:context=".MainActivity" >
</RelativeLayout>
这里面的代码非常简单 就一张图
启动界面代码
public class SplashActvity extends Activity {
private Handler mHandler = new Handler(){
public void handleMessage(android.os.Message msg) {
if(sp.getBoolean("frist", false)){
startAct(SplashActvity.this,MainActivity.class);
}else{
startAct(SplashActvity.this, GideActivty.class);
sp.edit().putBoolean("frist", true).commit();
finish();
}
};
};
private SharedPreferences sp;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_sp);
sp = this.getSharedPreferences("config", MODE_PRIVATE);
sendMessage();
}
private void sendMessage() {
mHandler.sendEmptyMessageDelayed(0, 2000);
}
protected void startAct(Context ct,
Class clazz) {
startActivity(new Intent(ct, clazz));
}
}
这里面也非常简单 主要是利用了消息机制 这个我们以后会讲到 发一个消息
然后第一次进入的时候 将数据存入首选项 否则走splash
最后别忘了清单文件中注册 其实 这个引导界面还有一些比较有趣的特效可以完成 比如我们在滑动的时候
小圆点是动态透明度改变的 这实际上是改变了alpha值 这些在应用里面都可以实现 以后有时间的话 我会继续进行讲解
包括一些3D界面的引导 (听上去是不是很炫酷呢)
好了 这次先到这里 估计有点多 大家慢慢看 咱下次再见
本期代码下载