Android手机应用开发实战(二) | 展示王者荣耀英雄信息的APP

删除英雄

点击主界面上列表中的某个英雄就可以进入到英雄详情页面,再点击右上角的额菜单就会弹出删除的选项,点击弹出对话框的确认按钮就会删除成功,返回英雄主界面

英雄详情页面的其他实现后面再讨论,主要讨论删除功能

GIF

保存结果

HeroDetail.java菜单事件的处理

如果点击了确定删除就会把当前的Hero通过EventBus传过去,并且设置它的deleted属性为true

    //删除
case R.id.action_hero_delete:
    final AlertDialog.Builder alertDialog = new AlertDialog.Builder(HeroDetail.this);
    alertDialog.setTitle("提示").setMessage("是否确定删除英雄: " + displayHero.getName() + "?").setPositiveButton("确认",
            new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    displayHero.setDeleted(true);
                    EventBus.getDefault().post(displayHero);
                    finish();

                }
            }).setNegativeButton("取消",null).create().show();
    break;

更新数据库

Fragment1中继续修改EventBus的处理

public void onMessageEvent(Hero h) {
......
else if(h.getDeleted()){
        h.setDeleted(false);
        if(mAdapter != null) {
            mAdapter.deleteItem(clickPosition);
            searchAdapter.remove(h.getName());
            searchAdapter.notifyDataSetChanged();
        }
        mySQLiteHelper.deleteHero(h);
        Log.d("删除英雄", "onMessageEvent: delete" + h.getName());
    }

英雄显示

再讨论修改英雄之前,必须要讨论一下英雄显示页面HeroDetail的问题

这里只是说明基本功能的实现

1542865546989

这里的英雄名称,英雄称号,英雄职业,生存能力等四个条都是可以修改的(但是需要点击编辑按钮)

这也就是为什么这些属性条会有看起来不是很友好的拖动圆点,因为它们本身就不是拿来看的,而是拿来用的

英雄图标,英雄海报也是可以点击的,然后就可以选择更改的图片

暂时还没做修改技能图标,技能描述,英雄语音,推荐装备的功能

英雄职业也只是一个输入框,而不是Spinner,就是可以输入根本不存在的职业

获取传递过来的英雄

//获取数据
displayHero = (Hero)getIntent().getSerializableExtra("Click_Hero");

人物音效播放

如果点击进入英雄页面会播放这个英雄的出场音效的话,那气氛肯定会不一样!

所以当时创建Hero类的时候就已经添加了人物语音,这里设置就行了

//音效
MediaPlayer mp = MediaPlayer.create(HeroDetail.this, Uri.parse(displayHero.getVoice()));
mp.start();
......
//记得要销毁
 @Override
    protected void onDestroy() {
        super.onDestroy();
        mp.release();
    } 

背景海报图片的变暗处理

不是简单放一个图片作为背景就可以的,如果不做处理的话会看不清字的,像这样

1542866177371

这里做了个最简单的处理,但是也想了好久好久,网上的答案也很少

其实就是在ImageButton的属性添加一个

android:foreground="#60000000"

平时颜色的十六进制只有六位

比如说白色是#FFFFFF,黄色是#FFFF00,黑色是#000000

那这里的前面两位是什么呢,叫Alpha通道值

简单讲就是透明度,00就是全透明,FF就是全不透,我这里选了个半透的60,并且颜色设置为黑色(000000),就达到了变暗的效果


                

属性条(DiscreteSeekBar)的设置

heroViability.setProgress(displayHero.getViability());

属性条(DiscreteSeekBar)的滑动监听事件

//监听数值改变
heroViability.setOnProgressChangeListener(new DiscreteSeekBar.OnProgressChangeListener() {
    @Override
    public void onProgressChanged(DiscreteSeekBar seekBar, int value, boolean fromUser) {
        heroViabilityValue.setText(String.valueOf(value));
    }
    @Override
    public void onStartTrackingTouch(DiscreteSeekBar seekBar) {
    }
    @Override
    public void onStopTrackingTouch(DiscreteSeekBar seekBar) {
    }
});

圆形技能图标

本身ImageButton是没有给你设置形状这个属性的,所以得自己绘制

drawable中新建hero_skill_button.xml



    
     //指定颜色
      //指定弧度

activity_hero_detail.xml中的ImageButton调用

android:background="@drawable/hero_skill_button"

不同技能的显示

通过点击不同的技能图标可以显示不同的技能详情(默认是一技能图标)

对于玩王者的人来说应该是被动技能哈哈哈

//默认显示一技能
skillDescription.setText(displayHero.getSkill1_description());
skill1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        skillDescription.setText(displayHero.getSkill1_description());
    }
});

修改英雄

点击英雄详情页面菜单栏的编辑就可以进入编辑模式了

GIF

查看模式和编辑模式

开始默认的时候是只能查看不能编辑的,通过调用下面自定义的两个方法进入不同的模式,非编辑模式就会取消EditText的焦点,还有设置ImageButton不可点击,并且取消滑动条(DiscreteSeekBar)的使能(setEnabled(false)

HeroDetail.java

void uneditMode(){
    //姓名一直不可更改
    //取消焦点
    heroName.setFocusable(false);
    heroName.clearFocus();
    heroName.setFocusableInTouchMode(false);
    heroAlias.setFocusable(false);
    heroAlias.setFocusableInTouchMode(false);
    heroCategory.setFocusable(false);
    heroCategory.setFocusableInTouchMode(false);
    heroImage.setClickable(false);
    heroIcon.setClickable(false);
    heroDifficulty.setEnabled(false);
    heroSkillDamage.setEnabled(false);
    heroAttackDamage.setEnabled(false);
    heroViability.setEnabled(false);
    //关闭键盘
    InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
    if (imm.isActive()) {
        imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, InputMethodManager.HIDE_NOT_ALWAYS);
    }
}

void editMode(){
    heroCategory.setFocusable(true);
    heroCategory.requestFocus();
    heroCategory.setFocusableInTouchMode(true);
    heroAlias.setFocusable(true);
    heroAlias.requestFocus();
    heroAlias.setFocusableInTouchMode(true);
    heroImage.setClickable(true);
    heroIcon.setClickable(true);
    heroDifficulty.setEnabled(true);
    heroSkillDamage.setEnabled(true);
    heroAttackDamage.setEnabled(true);
    heroViability.setEnabled(true);
}

菜单响应事件

仅仅调用了上面的方法进入编辑模式

//设置菜单点击事件
        toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem menuItem) {
                switch (menuItem.getItemId()) {
                        //编辑
                    case R.id.action_hero_edit:
                        editMode();
                        break;
                        .......

保存结果

在菜单响应事件里添加

    //保存
case R.id.action_hero_save:
    displayHero.setImage(image_uri);
    displayHero.setAlias(heroAlias.getText().toString());
    displayHero.setCategory(heroCategory.getText().toString());
    displayHero.setViability(heroViability.getProgress());
    displayHero.setAttack_damage(heroAttackDamage.getProgress());
    displayHero.setSkill_damage(heroSkillDamage.getProgress());
    displayHero.setDifficulty(heroDifficulty.getProgress());
    displayHero.setIcon(icon_uri);
    displayHero.setFavorite(heroFavorite.isChecked());
    //不可编辑
    uneditMode();
    displayHero.setModified(true);
    Toast.makeText(HeroDetail.this, "保存成功", Toast.LENGTH_SHORT).show();
    EventBus.getDefault().post(displayHero);
    finish();
    break;

可以看到,仍然是通过EventBus把数据传回去,而且这里用setModified方法设置了英雄的modified值,方便区分

更新数据库

回到fragment1.java中的public void onMessageEvent(Hero h),下列代码用于保存修改过的英雄

其实数据太多,我采取的方式是先删除再添加

public void onMessageEvent(Hero h) {
......
else if(h.getModified()){
    h.setModified(false);
    if(mAdapter != null) {
        mAdapter.updateSingleHero(clickPosition, h);
    }
    mySQLiteHelper.deleteHero(h);
    mySQLiteHelper.addHero(h);
    Log.d("修改英雄", "onMessageEvent: modify" + h.getName());
}
......
}


查找英雄

GIF

使用控件

fragment1.xml


我是把这个搜索框嵌入到ToolBar中的,比较省空间

选取的时候同样遇到了选择android.support.v7.widget.SearchView还是SearchView的问题,SearchView好像是比较新,最起码能预览,我也记不得为啥选择了这个,反正挺好用

这里有个background属性,表示下拉栏的样式

layout文件夹的search_list.xml中,其实也没写什么,就一个英雄名称,其实还可以自定义显示更多(头像啥的)



    

基础设置

//search
final SearchView searchView = view.findViewById(R.id.hero_edit_search);
searchView.setIconified(false);//设置searchView处于展开状态
searchView.onActionViewExpanded();// 当展开无输入内容的时候,没有关闭的图标
searchView.setQueryHint("输入查找的英雄名");//设置默认无内容时的文字提示
searchView.setIconifiedByDefault(false);//默认为true在框内,设置false则在框外
searchView.setIconified(false);//展开状态
searchView.clearFocus();//清除焦点
searchView.isSubmitButtonEnabled();//键盘上显示搜索图标

候选项的设置

我们发现SearchView实际上是一个View的集合,里面有个控件叫做AutoCompleteTextView

找到这个自动填充的组件并且

AutoCompleteTextView completeText = searchView.findViewById(R.id.search_src_text) ;
completeText.setTextColor(getResources().getColor(android.R.color.white));//设置内容文字颜色
completeText.setHintTextColor(getResources().getColor(R.color.gainsboro));//设置提示文字颜色
completeText.setThreshold(0);
searchAdapter = new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, mAdapter.getAllNames());
completeText.setAdapter(searchAdapter);
completeText.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView parent, View view,int position,long id){
        searchView.setQuery(searchAdapter.getItem(position),true);
    }
});

提交事件的处理

如果有这个英雄就跳到相应的详情页面,不然就会弹出错误的Toast

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
    @Override
    public boolean onQueryTextSubmit(String query){

        Hero temp_hero = mAdapter.getItemByName(query);
        if(temp_hero != null){
            Intent intent = new Intent(getActivity(), HeroDetail.class);
            Bundle bundle=new Bundle();
            bundle.putSerializable("Click_Hero", temp_hero);
            intent.putExtras(bundle);
            startActivityForResult(intent, 0);
        }else{
            Toast.makeText(getActivity(),"该英雄不存在" ,Toast.LENGTH_SHORT).show();
        }

        return false;
    }

    @Override
    public boolean onQueryTextChange(String newText){
        return false;
    }
});

图片轮换器的实现

GIF

在英雄的主界面会有一个类似广告页一样的图片轮换器,显示一些英雄的海报(后来做成了收藏的功能),它们会自动切换,并且会循环切换

布局

很简单,就是在fragment1.xml中的一个viewpager和一个空的LinerLayout

一个用来播放图片,一个用来显示指示图片的小圆点



填充图片和圆点

这里卸载了onResume方法中而不是onCreate方法,因为后来还要更新其中的图片

@Override
    public void onResume() {
        super.onResume();
        lastPointPosition = 0;
        isRunning = true;
        //view pager
        pointGroup = view.findViewById(R.id.point_group);
        imageViewPager = view.findViewById(R.id.hero_upper_pager);
        //添加图片列表
        imageArr = mAdapter.getAllFavoriteHeroes(mySQLiteHelper);
        imgList= new ArrayList<>();
        for (int i=0;i

刚开始我是把这段代码放在onStart中,但是我发现在fragment中来回切换时不会调用onStart方法的,只会调用onResume

自定义适配器

//图片轮换的适配器
private class ImagePagerAdapter extends PagerAdapter {
    /**
     * 获得页面的总数
     */
    @Override
    public int getCount() {
        return imageArr.length;
    }
    /**
     * 判断view和object的对应关系,如果当前要显示的控件是来之于instantiateItem方法创建的就显示,否则不显示
     * object 为instantiateItem方法返回的对象
     * 如果为false就不会显示该视图
     */
    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view==object;
    }
    /**
     * 实例化下一个要显示的子条目,获取相应位置上的view,这个为当前显示的视图的下一个需要显示的控件
     * container  view的容器,其实就是viewager自身
     * position   ViewPager相应的位置
     */
    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        container.addView(imgList.get(position));
        return imgList.get(position);
    }
    /**
     * 销毁一个子条目,object就为instantiateItem方法创建的返回的对象,也是滑出去需要销毁了的视图对象
     */
    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        container.removeView((View) object);
        object=null;
    }
}

自动播放

新建一个Handler用来处理自动滑动的事件

/**
 * 用于实现自动滑动
 */
@SuppressLint("HandlerLeak")
private Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        if(msg.what==1 && isRunning){
            //收到消息,开始滑动
            if(imageViewPager != null){
                int currentItem = imageViewPager.getCurrentItem();//获取当前显示的界面的索引
                //如果当前显示的是最后一个页面,就显示第一张,否则显示下一张
                if(currentItem==imgList.size()-1){
                    imageViewPager.setCurrentItem(0);
                }else{
                    imageViewPager.setCurrentItem(currentItem+1);
                }
            }

            //3ms后再发送消息,实现循环
            handler.sendEmptyMessageDelayed(1, 3000);
        }
    }
};

并且开始默认private boolean isRunning = true;

onCreate中设置时间间隔定时发送消息

//自动播放
handler.sendEmptyMessageDelayed(1, 3000);

收藏功能

根本思想就是讲图片轮换器中填充的图片换成所有已收藏英雄的海报

更新图片集合

所以需要数据更新,首先在fragment1.java中添加

@Override
public void onPause() {
    super.onPause();
    isRunning = false;
    pointGroup.removeAllViews();
    imageViewPager.removeAllViews();
    imageViewPager.clearOnPageChangeListeners();
}

让程序页面不在活动状态时就要停止轮换,并且移除所有图片

这也就是为什么要在onStart中初始化轮换器中的图片

imageArr = mAdapter.getAllFavoriteHeroes();

但是这个需要使用Adapter中的获取所有已收藏英雄图片的方法

//返回所有已收藏HERO图片
public String[] getAllFavoriteHeroes(){
    ArrayList favoriteHeroes = new ArrayList<>();
    for(Hero h : mDatas){
        if(h.getFavorite())
            favoriteHeroes.add(h.getImage());
    }
    return favoriteHeroes.toArray(new String[favoriteHeroes.size()]);
}

收藏按钮

利用了Hero类中的favorite属性

在英雄详情页面有一个收藏按钮,会有两种形态

GIF

同样也是来源于GitHub(源码地址)

点击显示处理

初始化收藏按钮的状态

//关于收藏
heroFavorite.setChecked(displayHero.getFavorite());

点击保存按钮后获取按钮的状态

displayHero.setFavorite(heroFavorite.isChecked());

因为是否被收藏 严格意义上讲不属于英雄内在属性,所以它一直都是可以点击的而不用点击编辑模式,但是需要点击保存才能更新到数据库

分类显示英雄

布局

使用了控件RadioGroup


    
    
    
    
    
    
    

代码

//改变显示的英雄类别
hero_category_select = view.findViewById(R.id.hero_category);
hero_category_select.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(RadioGroup group, int checkedId) {
        mAdapter.updateWithCategory(mySQLiteHelper.getAllHeroes(), ((RadioButton)view.findViewById(checkedId)).getText().toString());
    }
});

方法

//通过英雄职业改变显示的数据
public void updateWithCategory(List total, String category){
    mDatas.clear();
    if(category.equals("全部")){
        mDatas = total;
        notifyDataSetChanged();
    }
    for(Hero i: total){
        if(i.getCategory().equals(category))
            mDatas.add(i);
    }
    notifyDataSetChanged();
}

英雄推荐装备的跳转

1542892672396

其实Hero中只存储了英雄的推荐装备名称,在英雄详情页面需要调用数据库得到推荐装备的图标

//获取英雄装备
HeroSQLiteHelper sqLiteHelper = new HeroSQLiteHelper(this);
final Equipment equipment1 = sqLiteHelper.getEquipmentsWithName(displayHero.getEquip1());

设置显示图标

//技能图标
equip1.setImageResource(equipment1.getImage());

点击跳转到装备详情页面

equip1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(HeroDetail.this, equipment_detail.class);
        intent.putExtra("equipment_data", equipment1);
        startActivity(intent);
    }
});

软件图标设置

manifests中修改android:icon属性

1542892783035

软件启动页面设置

新加一个LaunchActivity.java

public class LaunchActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //加载启动界面
        setContentView(R.layout.activity_launch);
        Integer time = 10;    //设置等待时间,单位为毫秒
        Handler handler = new Handler();
        //当计时结束时,跳转至主界面
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                startActivity(new Intent(LaunchActivity.this, MainActivity.class));
                LaunchActivity.this.finish();
            }
        }, time);
    }
}

activity_launch.xml





修改manifests

设置LaunchAcitviy为最先启动的Activity


    
        
        
    

这样子其实能运行,但是!!!

启动页面很快结束后会有好久好久的白屏时间才载入主界面,应该是有很多耗时操作,参考网上安卓冷启动的博客,把主页面设置为透明色,这样子还会停留好久在启动页面,就好像是启动页面启动了好久一样,其实早就开始启动主页面了

将下面这一句加到Themestyle中去

true

如我的theme@style/NoTitle


按住Ctrl并且点击它跳到styles中,找到对应的style,修改


笔记

图片不能占据整个屏幕宽度

通过java代码动态获取屏幕的宽度,并且设置Image的宽度为屏幕的宽度,高度自适应(由xml中的android:scaleType="fitXY"确定),就可以让图片占据整个屏幕的宽度了

作用:好看很多!

//获取图片选择按钮
imageButton = findViewById(R.id.hero_add_image);
//设置宽度固定,高度自适应
//获取屏幕宽度
int screenWidth = getResources().getDisplayMetrics().widthPixels;
ViewGroup.LayoutParams lp = imageButton.getLayoutParams();
//宽度为屏幕宽度
lp.width = screenWidth;
//高度自适应
lp.height = LinearLayout.LayoutParams.WRAP_CONTENT;
imageButton.setLayoutParams(lp);
//最大允许宽度和高度
imageButton.setMaxWidth(screenWidth);
imageButton.setMaxHeight(2 * screenWidth / 3);

但是一定要记得xml中设置ImageButton的属性中加上一句

android:adjustViewBounds="true"

表示允许用代码调整视图

获取搜索框补全控件

网上某位大神用这个方法

intcompleteTextId=searchView.getResources().getIdentifier("android:id/search_src_text",null,null);

AutoCompleteTextViewcompleteText=searchView.findViewById(completeTextId);

其实没用,很简单的,用这个就好了

AutoCompleteTextViewcompleteText=searchView.findViewById(R.id.search_src_text);

报错

Sync Build:

添加依赖

implementation'com.android.support:support-v4:28.0.0'

保存图片的问题

之前用资源idint类型),后来需要自己添加的资源所以改用Uri

但是会出问题,因为Uri这个类型没有实现序列化,Intent传递数据需要支持序列化

1542872395711

所以就直接用了Uri转换的String,但是还是会出现权限不够的问题

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无法预览的问题

Preview 1 Error




换成





虚拟按键遮挡内容的问题

刚开始屏幕内的虚拟按键会遮挡住一部分视图(其实它是在ScrollView里面,但是滚动不了),可能它认为虚拟按键底下也是显示区域的一部分吧

0800 英 雄 攵 击 伤 害 : 技 能 伤 害 : 手 难 度 : 请 点 击 技 能 图 标 过 在 详 细 技 能 谲 点 击 技 能 标 查 详 细 技 能 请 点 击 技 能 留 忻 0 着 详 细 抟 能 请 点 击 能 留 柝 详 红 技 能 请 点 击 技 能 图 标 台 看 许 细 技 能 请 点 击 技 能 标 查 石 许 细 技 北 请 点 工 技 能 标 巷 详 细 技 北 惟 荐 出 装

网上的博客看了好多解决办法,什么隐藏任务栏,虚拟按键透明啥的,比如这篇博客

反正我没有解决,最后自己想了个超级简单的办法:

xml最底下加了个空白的View,也就是说让内容变长,即使我看不到最底下的View内容(本来就没内容),可那本来就不是我需要的啊,我只要能看到底下View上面的内容就好了

1542888946680

自己都觉得这个想法实在太棒了!

(但是我知道实际应用中还是不能这样的)

你可能感兴趣的:(Android手机应用开发实战(二) | 展示王者荣耀英雄信息的APP)