使用说明
系统
自定义了APP的图标
启动app查看到启动动画(图片)
P.S. 可能会出现启动动画消失回到桌面的情况,等待后进入app
进入app默认显示主界面,底部标签为英雄、装备、铭文、技能
进入app后播放背景音乐
英雄主界面
默认显示:搜索框,工具框(
ToolBar),添加英雄按钮,收藏英雄显示,英雄分类显示还有具体的英雄列表点击搜索框出现候选英雄列表(不输入会默认显示所有英雄列表,输入文字后会匹配输入文本为前缀的英雄名),点击搜索按钮或者候选框中的英雄名称进入英雄详情,如果搜索框中的英雄名不存在会弹出
Toast提示- 这是一个图片轮换器,理论上会3秒切换一个英雄(显示英雄海报),且支持循环切换(最后一个英雄切换回最前面一个),在英雄详情页面会有收藏与取消收藏的操作,下面的小圆点会指示当前图片的位置(红色指示)
- 添加英雄的按钮,点击之后会跳到添加英雄的页面
- 英雄职业的分类显示,点击不同的按钮就会过滤出该职业的英雄,更新英雄列表(搜索框提示列表不受影响)
- 英雄列表,网格布局,一共五列,只显示英雄头像,点击会进入英雄详情


英雄详情
- 返回主界面按钮
- 当前页面名称(英雄)
- 更多操作(编辑,删除,保存)
- 点击编辑之后,所有可修改的内容都处于可编辑状态(默认是不可编辑状态)
- 点击保存之后才能保存内容的修改(包括收藏!收藏之后直接返回是无效的,需要点击保存)
- 点击删除按钮之后弹出确认对话框,确定之后删除该英雄并且返回主界面
- 英雄头像(可修改)
- 英雄名字(不可修改)
- 英雄称号(可修改)
- 英雄职业(可修改)
- 英雄海报(可修改)
- 收藏英雄或者取消收藏(只能点击,不能修改)
- 英雄生存能力,攻击伤害,技能效果,上手难度数值显示(可修改)
- 英雄技能图标按钮,点击查看不同的技能(包括被动只有四个技能,天美最近的英雄技能越来越多了……)(不可修改)
- 英雄技能具体描述(不可修改)
- 英雄推荐出装(不可修改),点击会跳转到相应的装备详情页面



添加英雄
- 返回主界面
- 更多操作
- 添加头像,弹出系统图片选择框选择图片作为英雄头像(默认是:王者荣耀图标)
- 添加语音,弹出系统语音选择框作为英雄语音(默认是:PentaKill语音,享受五杀的感觉!)
- 选择英雄海报(默认是:当前图片)
- 填写英雄名字(不可为空,不可重复)
- 填写英雄称号(默认为:王者小兵)
- 填写英雄职业(默认为:法师)
- 填写英雄生存能力,攻击伤害,技能效果,上手难度等信息(范围是1-9,默认是1)


导航栏(SmartTabLayout)
使用了GitHub上的这个UI
https://github.com/ogaclejapan/SmartTabLayout

所以activity_main.xml中只有一个ViewPager和一个导航栏,(其实导航栏是在上面)
1 | <android.support.v4.view.ViewPager |
MainActivity.java
1 | FragmentPagerItemAdapter adapter = new FragmentPagerItemAdapter( |
这里的Fragment1,2,3,4分别是四个页面:英雄,装备,明文,技能
工具栏(ToolBar)
其实有两种ToolBar:
android.support.v7.widget.ToolbarToolbar
两个都能用,但是有一些区别
区别一:ToolBar的就是MenuItem显示不出来图标,另一个可以

区别二:android.support.v7.widget.Toolbar在设计页面没有预览,另一个有

区别三:android.support.v7.widget.Toolbar如果是来自其他Activity跳转过来的,它会自动加上标题,而且会自动有返回按钮,而且添加的菜单栏不太好设计(具体我也忘了),所以除了主界面,其他界面我都是用Toolbar,方便自己定制
使用方法:
fragment1.xml
1 | <android.support.v7.widget.Toolbar |
在Android视图下创建一个新的文件夹menu

新建hero_menu.xml,

用来保存ToolBar上的菜单栏布局

这里只有一个菜单,表示添加英雄,而且图标用定义的图标
showAsAction表示是否展开到工具栏上
always表示总是展开ifRoom表示如果有足够的空间就展开never表示总是不展开withText表示使菜单项和它的图标,菜单文本一起显示。
那么不展开的菜单会到哪里呢?
比如英雄详情页面,它会隐藏在更多按钮里(显示为三个点)

1 |
|
然后需要在Fragment1.java文件中动态设置ToolBar的样式,以及设置点击Item响应的事件,这里只有一个事件,就是跳转到添加英雄页面
1 | //获取ToolBar控件 |
还有其他的菜单布局,待会会使用
hero_detail_menu.xml
1 |
|
hero_add_menu.xml
1 |
|
java代码待会再讨论
英雄列表(RecyclerView)
英雄的实体类Hero.java
1 | public class Hero implements Serializable{ |
这里英雄的语音,头像,海报什么的资源文件全都是存储对应的Uri字符串
列表布局hero_list.xml
其实就显示了一个英雄头像
1 | <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" |
页面视图fragment1.xml
1 | <android.support.v7.widget.RecyclerView |
列表适配器HeroAdapter.java
这里仅仅实现了基本的显示功能,后面还会添加新的方法
1 | package com.team1.kingofhonor.adapter; |
代码文件Fragment1.java
这里设置成网格显示,一共有5列
1 | private View view; |
英雄数据库(HeroSQLiteHelper)
HeroSQLiteHelper.java
除了实现基本的功能外,主要是加了个getAllHeroes方法,这是为了最开始得到数据库的数据给HeroAdapter赋初值
1 | public class HeroSQLiteHelper extends SQLiteOpenHelper { |
这里我设置的主键是英雄名,查找起来可能会方便点吧,因此要求英雄不能重名
增加英雄
静态添加:
在MainActivity.java中会初始化一些英雄数据,放到数据库里
1 | protected void onCreate(Bundle savedInstanceState) { |
在Fragment1.java就可以通过数据库中的英雄数据填补Adapter
1 | mySQLiteHelper= new HeroSQLiteHelper(getContext()); |
动态添加:
在主界面点击右上角的加号可以进入到一个新的Activity

activity_hero_add.xml
1 |
|
HeroAdd.java
能添加的属性有英雄名称,英雄称号,英雄职业,英雄头像,英雄海报,英雄语音,英雄生存能力、攻击伤害、技能伤害、上手难度(没有添加技能)
其实大部分属性都可以不填就能添加成功,只有英雄名称一定要填写(因为且不止因为它是数据库表的主键),然后就有一个判断重名的问题,这是的做法是从Fagment1的ToolBar跳转过来的时候传入一个保存所有英雄名的数组,添加的时候如果新加的英雄名在这个数组里面就会添加失败!
1 | //比较无重复的英雄名称 |
当然需要在Adapter新加一个获取所有英雄名称的方法
1 | //返回所有的英雄数据 |
选取图片
有两个地方需要添加图片,一个直接点击屏幕上的海报图片选取添加英雄的图片,一个是右上角的菜单添加英雄的头像

以点击页面上的ImageButton(中间那个大图片)为例
通过下面的代码调用了系统的文件选择器,当然把文件类型局限在图片
这里的 startActivityForResult传递的参数是1,是为了跟另外一种选取图片的方式作区别,不然选择的图片不知道该作为英雄的海报还是英雄的头像
1 | imageButton.setOnClickListener(new View.OnClickListener() { |
重写onActivityResult方法,用来选取图片返回结果的处理
这里我做的处理就是把图片复制一份到该应用的file文件夹里(使用BitmapFactory),再保存新图片的Uri,其中新图片的名字由UUID生成,如果是海报的话还会更新页面上的图片按钮
不这么做而是直接保存选取图片的Uri会有两个问题
- 如果以后删除了相册了这个图片,那么引用就会有问题
- 更大的问题是其实这次对话结束后,以后再访问这个Uri的图片会出下权限错误,因为这个ACTION_GET_CONTENT权限是一次性的,后来即使知道Uri也不能访问
1 |
|
选取语音

在菜单栏里调用系统文件选取器
这里将ResultCode设置为3,跟前面选取图片作区分
1 | //添加语音 |
对返回的结果做解析,这里同样是复制音频,然后也会预览,预览的办法就是调用MediaPlayer播放这个音频
这也算遇到的一个挺大的问题吧,图片还可以通过
BitmapFactory编码解码,但是音频我还不知道怎么编码解码,好像网上很多人都是自己写方法调用,于是想了一个很简单的办法,就是直接把这个文件转换为字节流复制过去(所以这跟前面图片的复制本质上是不一样的)
1 | else if(requestCode == 3){ |
播放音频的后续工作就是记得在Activity销毁的时候记得销毁这个MediaPlayer
1 |
|
选择职业
这里使用了一个Spinner控件

1 | <Spinner |
展开它能选择的值定义在values文件夹中新建一个xml文件(文件名任意取)
1 |
|
获取选择的值也很简单
1 | final Spinner hero_category = findViewById(R.id.hero_add_category); |
选择生存能力等值
这里使用了一个GitHub的第三方控件org.adw.library.widgets.discreteseekbar.DiscreteSeekBar
原地址

1 | <org.adw.library.widgets.discreteseekbar.DiscreteSeekBar |
HeroAdd.java中获取选中的值
1 | final DiscreteSeekBar hero_viability = findViewById(R.id.hero_add_viability); |
保存结果
点击菜单中的保存按钮的事件
这里有三种情况
- 英雄名为空
- 英雄名重复(跟传进来的已有英雄数组比较)
- 添加成功
三种情况都会弹出Toast提示
1 | //设置菜单点击事件 |
但是主要问题是如何把添加的英雄传给主界面呢(因为这里不做英雄数据库的操作),
或者说通知主界面新添加了一个英雄呢(点击ToolBar的返回按钮或者虚拟按键返回会放弃添加英雄)
最简单的方法当然是EventBus
而且还有用到了Hero类的一个属性
1 | private Boolean added = false;//true表示是新加的 |
向下面这样子把新加的Hero传递回去,并且调用finish方法表示结束该Acitivity
这样子只要在Fragment1.java中的EventBus接收方法中判断英雄的added属性是否为true就可以判断是否要把这个英雄加到数据库里了
之所以要设置added这个变量是为了跟后面的deleted,modified分别表示的已删除,已修改作区分
根本宗旨就是数据库的更新全部放在英雄主界面(Fragment1)其它页面只能传递参数告诉它要如何更改数据库
1 | add_hero.setAdded(true); |
更新数据库
如上面讨论的这个得在Fragment1.java中实现
添加EventBus的处理方法
判断传递过来的Hero的added属性是否为true,是的话就添加到Adapter中
还要添加到SQLiteHelper中持久存储
1 | //EventBus |
删除英雄
点击主界面上列表中的某个英雄就可以进入到英雄详情页面,再点击右上角的额菜单就会弹出删除的选项,点击弹出对话框的确认按钮就会删除成功,返回英雄主界面
英雄详情页面的其他实现后面再讨论,主要讨论删除功能

保存结果
HeroDetail.java菜单事件的处理
如果点击了确定删除就会把当前的Hero通过EventBus传过去,并且设置它的deleted属性为true
1 | //删除 |
更新数据库
在Fragment1中继续修改EventBus的处理
1 | public void onMessageEvent(Hero h) { |
英雄显示
再讨论修改英雄之前,必须要讨论一下英雄显示页面
HeroDetail的问题这里只是说明基本功能的实现

这里的英雄名称,英雄称号,英雄职业,生存能力等四个条都是可以修改的(但是需要点击编辑按钮)
这也就是为什么这些属性条会有看起来不是很友好的拖动圆点,因为它们本身就不是拿来看的,而是拿来用的
英雄图标,英雄海报也是可以点击的,然后就可以选择更改的图片
暂时还没做修改技能图标,技能描述,英雄语音,推荐装备的功能
英雄职业也只是一个输入框,而不是
Spinner,就是可以输入根本不存在的职业
获取传递过来的英雄
1 | //获取数据 |
人物音效播放
如果点击进入英雄页面会播放这个英雄的出场音效的话,那气氛肯定会不一样!
所以当时创建Hero类的时候就已经添加了人物语音,这里设置就行了
1 | //音效 |
背景海报图片的变暗处理
不是简单放一个图片作为背景就可以的,如果不做处理的话会看不清字的,像这样

这里做了个最简单的处理,但是也想了好久好久,网上的答案也很少
其实就是在ImageButton的属性添加一个
android:foreground="#60000000"
平时颜色的十六进制只有六位
比如说白色是#FFFFFF,黄色是#FFFF00,黑色是#000000
那这里的前面两位是什么呢,叫Alpha通道值
简单讲就是透明度,00就是全透明,FF就是全不透,我这里选了个半透的60,并且颜色设置为黑色(000000),就达到了变暗的效果
1 | <!--背景图片变暗,想了好久!!!--> |
属性条(DiscreteSeekBar)的设置
1 | heroViability.setProgress(displayHero.getViability()); |
属性条(DiscreteSeekBar)的滑动监听事件
1 | //监听数值改变 |
圆形技能图标
本身ImageButton是没有给你设置形状这个属性的,所以得自己绘制
在drawable中新建hero_skill_button.xml
1 |
|
在activity_hero_detail.xml中的ImageButton调用
1 | android:background="@drawable/hero_skill_button" |
不同技能的显示
通过点击不同的技能图标可以显示不同的技能详情(默认是一技能图标)
对于玩王者的人来说应该是被动技能哈哈哈
1 | //默认显示一技能 |
修改英雄
点击英雄详情页面菜单栏的编辑就可以进入编辑模式了

查看模式和编辑模式
开始默认的时候是只能查看不能编辑的,通过调用下面自定义的两个方法进入不同的模式,非编辑模式就会取消EditText的焦点,还有设置ImageButton不可点击,并且取消滑动条(DiscreteSeekBar)的使能(setEnabled(false))
HeroDetail.java
1 | void uneditMode(){ |
菜单响应事件
仅仅调用了上面的方法进入编辑模式
1 | //设置菜单点击事件 |
保存结果
在菜单响应事件里添加
1 | //保存 |
可以看到,仍然是通过EventBus把数据传回去,而且这里用setModified方法设置了英雄的modified值,方便区分
更新数据库
回到fragment1.java中的public void onMessageEvent(Hero h),下列代码用于保存修改过的英雄
其实数据太多,我采取的方式是先删除再添加
1 | public void onMessageEvent(Hero h) { |
查找英雄

使用控件
fragment1.xml
1 | <android.support.v7.widget.SearchView |
我是把这个搜索框嵌入到ToolBar中的,比较省空间
选取的时候同样遇到了选择android.support.v7.widget.SearchView还是SearchView的问题,SearchView好像是比较新,最起码能预览,我也记不得为啥选择了这个,反正挺好用
这里有个background属性,表示下拉栏的样式
在layout文件夹的search_list.xml中,其实也没写什么,就一个英雄名称,其实还可以自定义显示更多(头像啥的)
1 |
|
基础设置
1 | //search |
候选项的设置
我们发现SearchView实际上是一个View的集合,里面有个控件叫做AutoCompleteTextView
找到这个自动填充的组件并且
1 | AutoCompleteTextView completeText = searchView.findViewById(R.id.search_src_text) ; |
提交事件的处理
如果有这个英雄就跳到相应的详情页面,不然就会弹出错误的Toast
1 | searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { |
图片轮换器的实现

在英雄的主界面会有一个类似广告页一样的图片轮换器,显示一些英雄的海报(后来做成了收藏的功能),它们会自动切换,并且会循环切换
布局
很简单,就是在fragment1.xml中的一个viewpager和一个空的LinerLayout
一个用来播放图片,一个用来显示指示图片的小圆点
1 | <android.support.v4.view.ViewPager |
填充图片和圆点
这里卸载了onResume方法中而不是onCreate方法,因为后来还要更新其中的图片
1 |
|
刚开始我是把这段代码放在
onStart中,但是我发现在fragment中来回切换时不会调用onStart方法的,只会调用onResume
自定义适配器
1 | //图片轮换的适配器 |
自动播放
新建一个Handler用来处理自动滑动的事件
1 | /** |
并且开始默认private boolean isRunning = true;
在onCreate中设置时间间隔定时发送消息
1 | //自动播放 |
收藏功能
根本思想就是讲图片轮换器中填充的图片换成所有已收藏英雄的海报
更新图片集合
所以需要数据更新,首先在fragment1.java中添加
1 |
|
让程序页面不在活动状态时就要停止轮换,并且移除所有图片
这也就是为什么要在onStart中初始化轮换器中的图片
1 | imageArr = mAdapter.getAllFavoriteHeroes(); |
但是这个需要使用Adapter中的获取所有已收藏英雄图片的方法
1 | //返回所有已收藏HERO图片 |
收藏按钮
利用了Hero类中的favorite属性
在英雄详情页面有一个收藏按钮,会有两种形态

同样也是来源于GitHub(源码地址)
点击显示处理
初始化收藏按钮的状态
1 | //关于收藏 |
点击保存按钮后获取按钮的状态
1 | displayHero.setFavorite(heroFavorite.isChecked()); |
因为是否被收藏 严格意义上讲不属于英雄内在属性,所以它一直都是可以点击的而不用点击编辑模式,但是需要点击保存才能更新到数据库
分类显示英雄
布局
使用了控件RadioGroup
1 | <RadioGroup |
代码
1 | //改变显示的英雄类别 |
方法
1 | //通过英雄职业改变显示的数据 |
英雄推荐装备的跳转

其实Hero中只存储了英雄的推荐装备名称,在英雄详情页面需要调用数据库得到推荐装备的图标
1 | //获取英雄装备 |
设置显示图标
1 | //技能图标 |
点击跳转到装备详情页面
1 | equip1.setOnClickListener(new View.OnClickListener() { |
软件图标设置
在manifests中修改android:icon属性

软件启动页面设置
新加一个LaunchActivity.java
1 | public class LaunchActivity extends AppCompatActivity { |
activity_launch.xml
1 |
|
修改manifests
设置LaunchAcitviy为最先启动的Activity
1 | <activity android:name=".LaunchActivity" |
这样子其实能运行,但是!!!
启动页面很快结束后会有好久好久的白屏时间才载入主界面,应该是有很多耗时操作,参考网上安卓冷启动的博客,把主页面设置为透明色,这样子还会停留好久在启动页面,就好像是启动页面启动了好久一样,其实早就开始启动主页面了
将下面这一句加到Theme的style中去
1 | <item name="android:windowIsTranslucent">true</item> |
如我的theme是@style/NoTitle
1 | <application |
按住Ctrl并且点击它跳到styles中,找到对应的style,修改
1 | <style name="NoTitle" parent="Theme.AppCompat.Light.NoActionBar"> |
笔记
图片不能占据整个屏幕宽度
通过java代码动态获取屏幕的宽度,并且设置Image的宽度为屏幕的宽度,高度自适应(由xml中的android:scaleType="fitXY"确定),就可以让图片占据整个屏幕的宽度了
作用:好看很多!
1 | //获取图片选择按钮 |
但是一定要记得在xml中设置ImageButton的属性中加上一句
android:adjustViewBounds="true"
表示允许用代码调整视图
获取搜索框补全控件
网上某位大神用这个方法
1 | intcompleteTextId=searchView.getResources().getIdentifier("android:id/search_src_text",null,null); |
其实没用,很简单的,用这个就好了
1 | AutoCompleteTextViewcompleteText=searchView.findViewById(R.id.search_src_text); |
报错

添加依赖
implementation'com.android.support:support-v4:28.0.0'
保存图片的问题
之前用资源id(int类型),后来需要自己添加的资源所以改用Uri,
但是会出问题,因为Uri这个类型没有实现序列化,Intent传递数据需要支持序列化

所以就直接用了Uri转换的String,但是还是会出现权限不够的问题
1 | java.lang.RuntimeException: Unable to start activity ComponentInfo{com.team1.kingofhonor/com.team1.kingofhonor.HeroDetail}: java.lang.SecurityException: Permission Denial: opening provider com.android.providers.downloads.DownloadStorageProvider from ProcessRecord{b82627f 23152:com.team1.kingofhonor/u0a85} (pid=23152, uid=10085) requires that you obtain access using ACTION_OPEN_DOCUMENT or related API |
最后只有复制一份到本软件的目录下
AndroidStudio无法预览的问题

把
1 | <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> |
换成
1 | <style name="AppTheme" parent="Base.Theme.AppCompat.Light.DarkActionBar"> |
虚拟按键遮挡内容的问题
刚开始屏幕内的虚拟按键会遮挡住一部分视图(其实它是在ScrollView里面,但是滚动不了),可能它认为虚拟按键底下也是显示区域的一部分吧

网上的博客看了好多解决办法,什么隐藏任务栏,虚拟按键透明啥的,比如这篇博客
反正我没有解决,最后自己想了个超级简单的办法:
在xml最底下加了个空白的View,也就是说让内容变长,即使我看不到最底下的View内容(本来就没内容),可那本来就不是我需要的啊,我只要能看到底下View上面的内容就好了

自己都觉得这个想法实在太棒了!
(但是我知道实际应用中还是不能这样的)