Android 项目:智慧北京(1)——闪屏与引导界面
上一篇文章介绍了整个“智慧北京”项目学习总结,接下来就是具体的开发步骤了,其实接下来写的几篇文章都是为了督促我再次独立的敲写一边代码,仅此而已。
项目源代码:http://download.csdn.net/detail/xwdoor/9430613
1 闪屏界面
这里利用旋转动画、渐变动画、缩放动画实现闪屏界面的开发。至于闪屏界面的作用,我估计是因为加载 MainActivity 时比较耗时,为了更好的用户体验,在初始化数据并显示之前,显示闪屏界面,数据初始化完成后,再进入操作界面。
以下是实现步骤:
建立项目:SmartBeijing16
以后都在此项目基础上添加代码
创建 Activity:SplashActivity
并设置为 Launcher Activity,同时创建相应的布局界面:activity_splash.xml
- 在布局界面 activity_splash 中,添加 ImageView,并设置背景和图片,如下所示。
<ImageView
android:id="@+id/iv_splash"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/splash_bg_newyear"
android:src="@drawable/splash_horse_newyear"/>
- 在 SplashActivity 的方法 onCreate() 中,取消标题栏:
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
- 创建并启动动画
//旋转动画
//以自身为中心旋转360度
RotateAnimation rotateAnimation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
//设置动画时间为2秒
rotateAnimation.setDuration(1000);
//显示动画完成后的视图
rotateAnimation.setFillAfter(true);
//缩放动画
//以自身为中心从0倍放大到1倍
ScaleAnimation scaleAnimation = new ScaleAnimation(0, 1, 0, 1, Animation.RELATIVE_TO_SELF, 1f, Animation.RELATIVE_TO_SELF, 1f);
scaleAnimation.setDuration(1000);
scaleAnimation.setFillAfter(true);
//渐变动画
AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
alphaAnimation.setDuration(2000);
alphaAnimation.setFillAfter(true);
//声明动画集
AnimationSet set = new AnimationSet(false);
//添加动画
set.addAnimation(rotateAnimation);
set.addAnimation(scaleAnimation);
set.addAnimation(alphaAnimation);
//获取界面中的控件
ImageView ivSplash = (ImageView) findViewById(R.id.iv_splash);
//开始动画
ivSplash.startAnimation(set);
创建两个包:utils 和 global
在 utils 工具包中创建类:PrefUtils,对 SharePreferences 进行简单的封装,用于存放配置数据;创建类: DensityUtils,用于 dp 与 px 单位的转换。
这几个类在接下来的步骤中需要用到,所以先写出来了。
//对 SharePreferences 进行简单的封装,用于存放配置数据
public class PrefUtils {
public static void setBoolean(Context ctx,String key,boolean value){
SharedPreferences preferences = ctx.getSharedPreferences("config", Context.MODE_PRIVATE);
preferences.edit().putBoolean(key,value).apply();
}
public static boolean getBoolean(Context ctx,String key,boolean defValue){
SharedPreferences preferences = ctx.getSharedPreferences("config", Context.MODE_PRIVATE);
return preferences.getBoolean(key,defValue);
}
public static void setInt(Context ctx,String key,int value){
SharedPreferences preferences = ctx.getSharedPreferences("config", Context.MODE_PRIVATE);
preferences.edit().putInt(key, value).apply();
}
public static int getInt(Context ctx,String key,int defValue){
SharedPreferences preferences = ctx.getSharedPreferences("config", Context.MODE_PRIVATE);
return preferences.getInt(key, defValue);
}
public static void setString(Context ctx,String key,String value){
SharedPreferences preferences = ctx.getSharedPreferences("config", Context.MODE_PRIVATE);
preferences.edit().putString(key, value).apply();
}
public static String getString(Context ctx,String key,String defValue){
SharedPreferences preferences = ctx.getSharedPreferences("config", Context.MODE_PRIVATE);
return preferences.getString(key, defValue);
}
}
/**
* 单位转换
* Created by XWdoor on 2016/2/13.
*/
public class DensityUtils {
public static float px2Dip(Context ctx, int px) {
float density = ctx.getResources().getDisplayMetrics().density;
return px / density;
}
public static int dip2Px(Context ctx, float dip) {
float density = ctx.getResources().getDisplayMetrics().density;
int px = (int) (dip * density + 0.5);
return px;
}
}
在 global 包中创建 Config 类,用于存放常量数据。
/**
* 常量数据
* Created by XWdoor on 2016/2/13.
*/
public class Config {
public static final String IS_FIRST_ENTER = "is_first";
}
2 引导界面
闪屏界面稍微复杂一点,主要使用 ViewPager 控件,然后用小圆点作为 ViewPager 的指示器,标识当前滑动的位置,难点在于小圆点的位置需要与滑动操作进行同步。这里也可以使用开源项目:ViewPagerIndicator,在后面的开发中我们会用到。
创建 Activity:GuideActivity
取消标题栏,并创建相应的布局文件:activity_guide.xml
- 在 MainActivity 和 GuideActivity 中添加启动方法 startAct(),如下所示。这是《第一行代码》中提到的方法,这样子在团队开发中就不用询问启动时都传了哪些参数,直接从这个方法中就可以得知。
public static void startAct(Context ctx) {
Intent intent = new Intent(ctx, GuideActivity.class);
ctx.startActivity(intent);
}
- 在 SplashActivity 中添加动画监听,在动画结束时,启动引导界面或主界面
set.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
boolean isFirst = PrefUtils.getBoolean(SplashActivity.this, Config.IS_FIRST_ENTER, true);
//判断是否是第一次进入
if (isFirst) {
GuideActivity.startAct(SplashActivity.this);
} else {
MainActivity.startAct(SplashActivity.this);
}
finish();
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
填充布局文件 activity_guide
- 在布局文件中添加 ViewPager,Button,以及自定义的小圆点(红色),小灰圆点需要与 ViewPager 的页数保持一致,所以需要在后台使用代码动态添加。
<android.support.v4.view.ViewPager
android:id="@+id/vp_Guide"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<Button
android:id="@+id/btn_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="60dp"
android:visibility="invisible"
android:textColor="@color/text_color_button_start"
android:background="@drawable/background_button_start"
android:text="@string/start"/>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="30dp">
<LinearLayout
android:id="@+id/ll_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"/>
<ImageView
android:id="@+id/iv_red_point"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/shape_point_red"/>
</RelativeLayout>
- 这里的小红点与小灰点用到了自定义形状:Shape — oval,方法是在 drawable 文件夹下创建两个资源文件:shape_point_gray.xml 和 shape_point_red.xml。
shape_point_gray.xml:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size android:width="10dp" android:height="10dp"/>
<solid android:color="@android:color/darker_gray"/>
</shape>
shape_point_red.xml:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<size android:width="10dp" android:height="10dp"/>
<solid android:color="@android:color/holo_red_light"/>
</shape>
- 开始体验按钮用到了自定义样式,分别创建 Color 资源文件:text_color_button_start.xml,以及 drawable 资源文件:background_button_start.xml
text_color_button_start.xml:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@android:color/black" android:state_pressed="true"/>
<item android:color="@android:color/white"/>
</selector>
background_button_start.xml:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/button_red_pressed"/>
<item android:drawable="@drawable/button_red_normal"/>
</selector>
在 GuideActivity 中初始化数据
- 创建适配器:GuideAdapter,用于 ViewPager 的数据适配
class GuideAdapter extends PagerAdapter {
@Override
public int getCount() {
return listImageView.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
//初始化item
@Override
public Object instantiateItem(ViewGroup container, int position) {
ImageView view = listImageView.get(position);
container.addView(view);
return view;
}
//回收item资源
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
}
- 创建方法 initData(),对 ViewPager 中的引导图片进行初始化,并添加小灰圆点指示器
//初始化数据
private void initData() {
//图片资源数组
int[] imageIds = new int[]{R.drawable.guide_1, R.drawable.guide_2, R.drawable.guide_3};
listImageView = new ArrayList<>();
for (int i = 0; i < imageIds.length; i++) {
//添加引导图
ImageView view = new ImageView(this);
view.setImageResource(imageIds[i]);
view.setScaleType(ImageView.ScaleType.FIT_XY);
listImageView.add(view);
//添加圆点指示器
ImageView pointView = new ImageView(this);
pointView.setImageResource(R.drawable.shape_point_gray);
if (i > 0) {//从第二个圆点开始设置左间距
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
params.leftMargin = DensityUtils.dip2Px(this, 10);
pointView.setLayoutParams(params);
}
llContainer.addView(pointView);
}
//计算两个圆点的距离:圆点本身的直径+间距
pointDis = DensityUtils.dip2Px(this, 10 + 10);
Log.i("123123", "pointDis-->" + pointDis);
vpGuide.setAdapter(new GuideAdapter());
}
- 创建方法:initListener(),初始化 ViewPager 的滑动事件以及开始体验按钮的点击事件,在滑动事件中控制小圆点与滑动操作同步,以及按钮的显示与隐藏;在点击事件中需要设置标识,下次启动 App 时不再进入引导界面。
//初始化监听
private void initListener() {
//设置ViewPager滑动监听
vpGuide.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
Log.i("123123", "positionOffset-->" + positionOffset + ",positionOffsetPixels-->" + positionOffsetPixels);
//获取布局参数
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) ivRedPoint.getLayoutParams();
//设置小红点的偏移量
params.leftMargin = (int) (positionOffset * pointDis) + position * pointDis;
ivRedPoint.setLayoutParams(params);
}
@Override
public void onPageSelected(int position) {
//当滑动到最后一页时,才显示按钮
if (position == listImageView.size() - 1) {
btnStart.setVisibility(View.VISIBLE);
} else {
btnStart.setVisibility(View.INVISIBLE);
}
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//启动主页面
MainActivity.startAct(GuideActivity.this);
//设置标志位,下次进入跳过引导界面
PrefUtils.setBoolean(GuideActivity.this, Config.IS_FIRST_ENTER,false);
finish();
}
});
}
3 总结
到此,闪屏界面和引导界面就开发完了,接下来就要进入主界面开发了。
春节将近一星期没有碰电脑,也没有敲代码,还有点手生了,呵呵…
通过这次敲代码,有两个地方还是不太熟悉,一个就是资源文件的创建与编写,就是自定义小圆点的地方,是以 Shape 为根节点;还有一个就是小圆点指示器的布局,总是想之前是怎么实现的,而不是忘掉以前,从新写出实现方式。
不一样的地方:小圆点的计算方式,我是通过单位换算得到小灰圆点之间的距离,没有调用系统的视图树,添加布局监听来计算。