第 4 章 3D 相册 学习目标 u 掌握相册界面的开发, 实现相册的立体与倒影效果 在现实生活中, 我们浏览照片时, 不同的相册软件有不同的浏览效果 本章主要实现了一个 3D 效果的相册, 该相册中的图片可以循环滑动展示 4.1 3D 相册 任务综述 相册界面主要是展示一组图片的立体效果, 并显示每个图片的倒影, 左右滑动可以切换不同的图片到 界面的中间位置, 同时在图片下方显示当前图片的标题, 点击界面上的任意一个图片时, 会弹出该图片的 标题信息 任务 4-1 相册界面 任务分析 相册界面由一组图片与一个图片标题组成, 界面效果如图 4-1 所示 任务实施 (1) 创建项目 图 4-1 3D 相册界面 首先创建一个项目, 将其命名为 3DAlbum, 指定包名为 com.itheima.album,activity 名为 AlbumActivity, 对应布局名为 activity_album 注意 :
本项目的 build.gradle 文件中的 minsdkversion 设置为 17 (2) 导入界面图片在 Android Studio 中, 切换到 Project 选项卡, 在 res 文件夹中创建一个 drawable-hdpi 文件夹, 将相册界面所需要的图片 img_1.png img_2.png img_3.png img_4.png img_5.png 导入到 drawable-hdpi 文件夹中, 将项目的 icon 图标 album_icon.png 导入到 mipmap-hdpi 文件夹中 (3) 引入 library 库 3D 相册是通过引入第三方库 library 中的 FeatureCoverFlow 控件来显示的 在 Android Studio 中, 选择 File à New à Import Module 选项, 把第三方库 library 导入项目中, 选中项目右击选择 Open Module Settings 选项, 接着选择 Dependencies 选项卡, 点击右上角的绿色加号并选择 Module Dependency 选项, 把 library 库加入主项目,library 库的详细信息如图 4-2 所示 图 4-2 library 库 (4) 放置界面控件在布局文件中, 放置 1 个自定义控件 FeatureCoverFlow 用于显示相册,1 个 TextSwitcher 控件用于显示图片的标题, 具体代码如 文件 4-1 所示 文件 4-1 activity_album.xml 1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:coverflow="http://schemas.android.com/apk/res-auto" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:background="@android:color/darker_gray"> 6 <com.itheima.coverflow.ui.featurecoverflow 7 android:id="@+id/fcf_coverflow" 8 android:layout_width="match_parent" 9 android:layout_height="match_parent" 10 coverflow:coverheight="@dimen/album_height" 11 coverflow:coverwidth="@dimen/album_width" 12 coverflow:maxscalefactor="1.5" 13 coverflow:reflectiongap="0px"
14 coverflow:rotationthreshold="0.5" 15 coverflow:scalingthreshold="0.5" 16 coverflow:spacing="0.6" /> 17 <TextSwitcher 18 android:id="@+id/ts_title" 19 android:layout_width="match_parent" 20 android:layout_height="wrap_content" 21 android:layout_alignparentbottom="true" 22 android:layout_centervertical="true" 23 android:paddingbottom="16dp" /> 24 </RelativeLayout> (5) 修改 dimens.xml 文件 由于相册界面中相册控件的宽高是固定的, 因此需要在 res/values 文件夹中的 dimens.xml 文件中添加 相册的宽高, 具体代码如下所示 : <dimen name="album_width">120dp</dimen> <dimen name="album_height">180dp</dimen> 任务 4-2 相册界面 Item 任务分析 由于相册界面使用的是 FeatureCoverFlow 控件展示图片组合的, 因此需要创建一个该组合的 Item 界面, Item 界面主要是显示一个图片, 界面效果如图 4-3 所示 图 4-3 相册界面 Item 任务实施 (1) 创建相册界面 Item 在 res/layout 文件夹中, 创建布局文件 item_album.xml (2) 放置界面控件在布局文件中, 放置 1 个 ImageView 控件用于显示相册中的图片, 具体代码如 文件 4-2 所示
文件 4-2 item_album.xml 1 <?xml version="1.0" encoding="utf-8"?> 2 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="@dimen/album_width" 4 android:layout_height="@dimen/album_height" 5 android:clickable="true" 6 android:foreground="@drawable/album_selector"> 7 <ImageView 8 android:id="@+id/iv_img" 9 android:layout_width="match_parent" 10 android:layout_height="match_parent" 11 android:layout_centerhorizontal="true" 12 android:layout_centervertical="true" 13 android:scaletype="centercrop" /> 14 </FrameLayout> (3) 创建背景选择器相册界面中的每个图片在被按下与弹起时, 图片背景会有明显的区别, 这种效果可以通过背景选择器进行实现 首先选中 drawable 文件夹, 右键选择 New à Drawable resource file 选项, 创建一个背景选择器 album_selector.xml, 根据图片被按下与弹起时的状态来切换它的背景颜色, 当图片被按下时显示灰色背景 ( 颜色值是 #96000000 ), 当图片弹起时显示透明背景, 具体代码如 文 4-3 所示 文件 4-3 album_selector.xml 1 <?xml version="1.0" encoding="utf-8"?> 2 <selector xmlns:android="http://schemas.android.com/apk/res/android"> 3 <item android:state_pressed="true"> 4 <color android:color="#96000000" /> 5 </item> 6 <item> 7 <color android:color="@android:color/transparent" /> 8 </item> 9 </selector> (4) 创建 item_title.xml 文件由于在相册界面的布局中放置了一个 TextSwitcher 控件来显示图片的标题, 该控件在实现 ViewFactory 接口中的 makeview() 方法时必须返回一个 TextView 控件, 因此我们需要在 res/layout 文件夹中创建一个 item_title.xml 文件来放置返回的 TextView 控件, 具体代码如 文件 4-4 所示 文件 4-4 item_title.xml 1 <?xml version="1.0" encoding="utf-8"?> 2 <TextView xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="wrap_content" 5 android:gravity="center" 6 android:textappearance="?android:textappearancelargeinverse" 7 android:textcolor="@android:color/white" />
任务 4-3 创建 AlbumBean 任务分析 由于相册界面主要是显示一组图片, 每个图片都有图片 Id 和标题 Id 属性, 因此可以创建一个 AlbumBean 类来存放图片的这些属性 任务实施 在 com.itheima.album 包中创建一个 AlbumBean 类 在该类中创建图片属性, 具体代码如 文件 4-5 所示 文件 4-5 AlbumBean.java 1 package com.itheima.album; 2 public class AlbumBean { 3 public int imgresid; // 图片 Id 4 public int titleresid; // 图片标题 Id 5 public AlbumBean(int imgresid, int titleresid) { 6 this.imgresid = imgresid; 7 this.titleresid = titleresid; 8 } 9 } 任务 4-4 相册界面 Adapter 任务分析 相册界面中显示的一组图片是通过 FeatureCoverFlow 控件来实现的, 因此需要创建一个数据适配器 AlbumAdapter 对该控件进行数据适配 任务实施 (1) 创建 AlbumAdapter 类在 com.itheima.album 包中, 创建一个 AlbumAdapter 类继承 BaseAdapter 类, 并重写 getcount () getitem () getitemid () getview() 方法 (2) 创建 ViewHolder 类在 AlbumAdapter 类中创建一个 ViewHolder 类来创建 Item 界面上的图片控件对应的字段 (3) 设置 Item 界面数据在 getview() 方法中, 通过 inflate() 方法加载相册界面的 Item 布局文件 item_album.xml, 接着将图片设置到界面控件上, 具体代码如 文件 4-6 所示 文件 4-6 AlbumAdapter.java 1 package com.itheima.album; 2 import android.content.context; 3 import android.view.layoutinflater; 4 import android.view.view; 5 import android.view.viewgroup;
6 import android.widget.baseadapter; 7 import android.widget.imageview; 8 import java.util.arraylist; 9 public class AlbumAdapter extends BaseAdapter { 10 private ArrayList<AlbumBean> datalist = new ArrayList<>(); 11 private Context mcontext; 12 public AlbumAdapter(Context context) { 13 mcontext = context; 14 } 15 public void setdata(arraylist<albumbean> datalist) { 16 this.datalist = datalist; 17 } 18 @Override 19 public int getcount() { 20 return datalist.size(); 21 } 22 @Override 23 public Object getitem(int position) { 24 return datalist.get(position); 25 } 26 @Override 27 public long getitemid(int position) { 28 return position; 29 } 30 @Override 31 public View getview(int position, View convertview, ViewGroup parent) { 32 if (convertview == null) { 33 LayoutInflater inflater = (LayoutInflater) mcontext. 34 getsystemservice(context.layout_inflater_service); 35 convertview = inflater.inflate(r.layout.item_album, null); 36 ViewHolder viewholder = new ViewHolder(); 37 viewholder.iv_img = (ImageView) convertview.findviewbyid(r.id.iv_img); 38 convertview.settag(viewholder); 39 } 40 ViewHolder holder = (ViewHolder) convertview.gettag(); 41 holder.iv_img.setimageresource(datalist.get(position).imgresid); 42 return convertview; 43 } 44 public class ViewHolder { 45 public ImageView iv_img; 46 } 47 }
任务 4-5 相册界面逻辑代码 任务分析 相册界面主要是显示一组图片, 滑动界面上的图片可以切换不同的图片到界面的中间位置, 同时在图片下方会显示该图片的标题, 点击界面上的任意一个图片, 会弹出对应图片的标题信息 任务实施 (1) 设置界面数据在 AlbumActivity 中创建一个 initdata() 方法, 在该方法中创建界面上需要的图片数据 (2) 获取界面控件在 AlbumActivity 中创建一个 init() 方法, 在该方法中获取相册界面的控件并设置相册控件的点击事件与滑动事件 (3) 双击后退键退出程序在 AlbumActivity 中重写 onkeydown() 方法, 在该方法中判断两次点击后退键的时间间隔是否小于 2 秒, 如果小于 2 秒, 则退出该程序 ; 如果大于 2 秒, 则提示 再按一次退出 3D 相册 具体代码如 文件 4-7 所示 文件 4-7 AlbumActivity.java 1 package com.itheima.album; 2 import android.os.bundle; 3 import android.support.v7.app.appcompatactivity; 4 import android.view.keyevent; 5 import android.view.layoutinflater; 6 import android.view.view; 7 import android.view.animation.animation; 8 import android.view.animation.animationutils; 9 import android.widget.adapterview; 10 import android.widget.textswitcher; 11 import android.widget.textview; 12 import android.widget.toast; 13 import android.widget.viewswitcher; 14 import com.itheima.coverflow.ui.featurecoverflow; 15 import java.util.arraylist; 16 public class AlbumActivity extends AppCompatActivity { 17 private FeatureCoverFlow coverflow; 18 private AlbumAdapter adapter; 19 private ArrayList<AlbumBean> datalist; 20 private TextSwitcher mtitle; 21 @Override 22 protected void oncreate(bundle savedinstancestate) { 23 super.oncreate(savedinstancestate); 24 setcontentview(r.layout.activity_album); 25 initdata();
26 initview(); 27 } 28 /** 29 * 初始化界面控件 30 */ 31 private void initview() { 32 mtitle = (TextSwitcher) findviewbyid(r.id.ts_title); 33 mtitle.setfactory(new ViewSwitcher.ViewFactory() { 34 @Override 35 public View makeview() { 36 LayoutInflater inflater = LayoutInflater.from(AlbumActivity.this); 37 TextView title = (TextView) inflater.inflate(r.layout.item_title, null); 38 return title; 39 } 40 }); 41 Animation in = AnimationUtils.loadAnimation(this, R.anim.slide_in_top); 42 Animation out = AnimationUtils.loadAnimation(this, R.anim.slide_out_bottom); 43 mtitle.setinanimation(in); 44 mtitle.setoutanimation(out); 45 coverflow = (FeatureCoverFlow) findviewbyid(r.id.fcf_coverflow); 46 adapter = new AlbumAdapter(this); 47 adapter.setdata(datalist); 48 coverflow.setadapter(adapter); 49 coverflow.setonitemclicklistener(new AdapterView.OnItemClickListener() { 50 @Override 51 public void onitemclick(adapterview<?> parent, View view, int position, long 52 id) { 53 if (position < datalist.size()) { 54 Toast.makeText(AlbumActivity.this, 55 getresources().getstring(datalist.get(position).titleresid), 56 Toast.LENGTH_SHORT).show(); 57 } 58 } 59 }); 60 coverflow.setonscrollpositionlistener(new FeatureCoverFlow. 61 OnScrollPositionListener() { 62 @Override 63 public void onscrolledtoposition(int position) { 64 mtitle.settext(getresources().getstring(datalist.get(position). 65 titleresid)); 66 } 67 @Override 68 public void onscrolling() { 69 mtitle.settext("");
70 } 71 }); 72 } 73 /** 74 * 初始化界面数据 75 */ 76 private void initdata() { 77 datalist = new ArrayList<>(); 78 datalist.add(new AlbumBean(R.drawable.img_1, R.string.title1)); 79 datalist.add(new AlbumBean(R.drawable.img_2, R.string.title2)); 80 datalist.add(new AlbumBean(R.drawable.img_3, R.string.title3)); 81 datalist.add(new AlbumBean(R.drawable.img_4, R.string.title4)); 82 datalist.add(new AlbumBean(R.drawable.img_5, R.string.title5)); 83 } 84 protected long exittime;// 记录第一次点击时的时间 85 @Override 86 public boolean onkeydown(int keycode, KeyEvent event) { 87 if (keycode == KeyEvent.KEYCODE_BACK 88 && event.getaction() == KeyEvent.ACTION_DOWN) { 89 if ((System.currentTimeMillis() - exittime) > 2000) { 90 Toast.makeText(AlbumActivity.this, " 再按一次退出 3D 相册 ", 91 Toast.LENGTH_SHORT).show(); 92 exittime = System.currentTimeMillis(); 93 } else { 94 AlbumActivity.this.finish(); 95 System.exit(0); 96 } 97 return true; 98 } 99 return super.onkeydown(keycode, event); 100 } 101 } (4) 设置标题进出动画 在显示图片标题时, 标题信息会从下往上缓缓进入界面, 当标题消失时, 标题信息会从上往下缓缓离 开界面, 因此首先需要在 res 文件夹中创建一个 anim 文件夹, 然后在该文件夹中分别创建 slide_in_top.xml 文件与 slide_out_bottom.xml 文件来实现标题信息显示与消失对应的动画效果, 具体代码如 文件 4-8 与 文件 4-9 文件 4-8 slide_in_top.xml 1 <?xml version="1.0" encoding="utf-8"?> 2 <set xmlns:android="http://schemas.android.com/apk/res/android"> 3 <translate 4 android:duration="@android:integer/config_mediumanimtime" 5 android:fromydelta="100%p" 6 android:toydelta="0" />
7 <alpha 8 android:duration="@android:integer/config_mediumanimtime" 9 android:fromalpha="0.0" 10 android:toalpha="1.0" /> 11 </set> 文件 4-9 slide_out_bottom.xml 1 <?xml version="1.0" encoding="utf-8"?> 2 <set xmlns:android="http://schemas.android.com/apk/res/android"> 3 <translate 4 android:duration="@android:integer/config_mediumanimtime" 5 android:fromydelta="0" 6 android:toydelta="100%p" /> 7 <alpha 8 android:duration="@android:integer/config_mediumanimtime" 9 android:fromalpha="1.0" 10 android:toalpha="0.0" /> 11 </set> (5) 修改 strings.xml 文件由于相册界面的图片组合中一共有 5 个图片, 每个图片都有对应的标题, 因此需要在 res/values 文件夹中的 strings.xml 文件中创建 5 个图片对应的标题, 具体代码如下所示 <string name="title1">girl</string> <string name="title2">spring Scenery</string> <string name="title3">summer Scenery</string> <string name="title4">autumn Scenery</string> <string name="title5">winter Scenery</string> (6) 修改清单文件由于本项目有自己的图标, 因此需要设置清单文件中 <application> 标签的 icon 属性为 @mipmap/ album_icon 由于项目创建时默认都带有一个标题栏不够美观, 因此需要设置清单文件中的 <application> 标签中的 theme 属性为 @style/theme.appcompat.light.noactionbar, 去掉标题栏 由于相册界面处于横屏时界面相对较清晰, 因此需要设置 AlbumActivity 对应的 <activity> 标签中的 screenorientation 属性为 landscape, 固定该界面为横屏 4.2 本章小结 本章主要讲解了 3D 效果的相册的实现过程, 在这个实现过程中主要是引入一个 library 库, 调用该库中的自定义控件 FeatureCoverFlow 来显示界面上的相册, 同时该控件也可以使图片水平滑动, 然后通过 TextSwitcher 控件来显示图片的标题, 最终实现整个 3D 效果的相册 在相册效果实现过程中, 代码量相对较少, 逻辑简单, 读者按照步骤完成即可 思考题 1. 请思考如何设置 3D 相册界面中每个图片的标题? 2. 请思考如何把图片设置到 3D 相册界面上?