欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android实战【可可爱爱一零一动植物志】(开发)

程序员文章站 2022-03-12 09:12:45
开发进展2020.7.1 开始页面FrameLayout:下层ImageView,纵向不够长,所以用了这三句代码的组合 android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop"使得图片以中心为基准不变形放大,直到纵向充满屏幕,横向左右两边超出屏幕部分被裁掉。上层放置了一个ProcessBar组件,后台运行一个副线程用于加载文件(但现在还没有...

开发进展

  • 2020.7.1 开始页面
    FrameLayout:下层ImageView,纵向不够长,所以用了这三句代码的组合

     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:scaleType="centerCrop"
    

    使得图片以中心为基准不变形放大,直到纵向充满屏幕,横向左右两边超出屏幕部分被裁掉。
    上层放置了一个ProcessBar组件,后台运行一个副线程用于加载文件(但现在还没有可加载的东西,所以暂时用随机数代替)。Handler对象负责接收副线程发来的消息以更新进度,并且判断加载是否完成:

     mHandler=new Handler(){
         @Override
         public void handleMessage(@NonNull Message msg) {
             if(msg.what==0x111){
                 progressBar.setProgress(mProgress);
             }else{
                 Toast.makeText(MainActivity.this,"耗时操作已完成",Toast.LENGTH_SHORT).show();
                 progressBar.setVisibility(View.GONE);
                 Intent intent=new Intent(MainActivity.this,Moment.class);
                 startActivity(intent);
             }
         }
     };
    

    效果:
    Android实战【可可爱爱一零一动植物志】(开发)

  • 2020.7.4 底部导航栏
    创建一个新的Activity。方便起见,我使用了Android自带的Activity模板:
    Android实战【可可爱爱一零一动植物志】(开发)创建完成后,可以看到Project中多出这些文件:
    Android实战【可可爱爱一零一动植物志】(开发)
    *说明:其实Android自带的底部导航栏Activity只有三栏,但由于我的项目需要四栏,所以上图中与“my”相关的文件是我自己添加的。特别注意的是,添加时不是只复制粘贴就完事的,不仅需要增添文件,还要修改很多文件内部的东西。由于我编写之前没有截图,所以下文中就假装Android自带四栏,我主要是要讲明白这几个文件之间的关系时怎样的。
    这些文件中,我比较熟悉的是Java文件Moment,layout包中xml文件activity_moment.xml。在activity_moment.xml文件中可以看到两个组件:一个是BottomNavigationView,一个是fragment。其中,底部导航栏中 app:menu="@menu/bottom_nav_menu"说明控制底部导航栏的代码在bottom_nav_menu文件中体现;fragment中app:navGraph="@navigation/mobile_navigation"则将fragment相关代码指向mobile_navigation文件。
    进入bottom_nav_menu文件,看到了四个相似的item:

     <item
     android:id="@+id/navigation_home"
     android:icon="@drawable/moment"
     android:title="@string/title_home" />
    

    从icon的设定可以确定,这是在设定底部导航栏每一个按钮的属性。所以我找了适合我的项目的图标,替换了原有的icon资源文件,并在value包中找到strings,更改了按钮的文字信息。
    然后进入mobile_navigation文件,看到了四个相似的fragment:

     <fragment
     android:id="@+id/navigation_home"
     android:name="com.example.a101.ui.home.HomeFragment"
     android:label="@string/title_home"
     tools:layout="@layout/fragment_home" />
    

    其中,layout指向了一个布局文件。进入该文件,看到它的全局设定中有一行:tools:context=".ui.home.HomeFragment"。打开home包,有两个Java文件,其中HomeFragment是Fragment的子类,HomeViewModel是ViewModel的子类。HomeFragment中onCreateView方法中homeViewModel=ViewModelProviders.of(this).get(HomeViewModel.class);将二者联系起来:ViewModelProviders的of(this)方法为当前fragment创建一个ViewModelProvider,ViewModelProvider的get(Class modelClass)为这个ViewModelProvider获取或创建一个与之相连的ViewModel,而homeViewModel就是ViewModel子类的实例对象。ViewModel的用处是获取并储存一个Activity或Fragment的有用数据。当这个Activity或Fragment由于构造发生变化而销毁的时候ViewModel可以留存下来;另外,ViewModel可以用于Activity中多个Fragment的数据共享。inflater的作用是从资源文件中找到特定的布局文件,然后就可以用熟悉的findViewById找想要的组件了。后面用到的LiveData、MutableLiveData、observer我实在没搞懂,暂时放在这里了。
    效果:
    Android实战【可可爱爱一零一动植物志】(开发)

- 2020.7.6 设置ActionBar
ActionBar是这个东西:
Android实战【可可爱爱一零一动植物志】(开发)
从Android3.0及之后的版本中,ActionBar都是自带的,就是说你完全不用敲相关的代码,Activity运行的时候它也会出现的。不过,Android自带的ActionBar就像上图所示,单单一个标题,什么组件也没有。然而我们经常需要在ActionBar中放一些按钮,比如朋友圈分享的小相机、返回按钮、筛选按钮等等,来满足更多需求,这就需要我们自定义ActionBar了。
给Activity自定义ActionBar,需要三个步骤:
① 上网找到合适的图标;
② 在res中创建一个menu包,里面创建一个xml类型的菜单文件,并添加一个item组件,在组件中设置图标、标题、位置等:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item android:id="@+id/share"
        android:icon="@drawable/share_white"
        android:title="share"
        app:showAsAction="always"></item>
</menu>

③ 在Activity的Java文件中解析菜单文件:

        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
           MenuInflater inflater=getMenuInflater(); //实例化一个MenuInflater对象
           inflater.inflate(R.menu.menu,menu); //解析菜单文件
            return super.onCreateOptionsMenu(menu);
        }

运行起来,确实在ActionBar中出现了用于分享的小图标。然而,对于每一个fragment,它们ActionBar中的需要满足不同的功能,可是上述做法却给所有fragment的ActionBar添加了分享图标。
Android实战【可可爱爱一零一动植物志】(开发)
究其原因,我们刚才是在Activity的Java文件中解析的菜单文件,而Activity的ActionBar是这个Activity下所有fragment共有的。
那么就需要找到一个在fragment自己的Java文件中解析菜单文件的方法:
① 保持菜单文件不变,注释掉刚刚在Activity的Java文件中添加的代码;
② 进入ui包中某一fragment的包中XxxxFragment文件,在onCreateView方法中添加一句setHasOptionsMenu(true);
③ 在onCreateView后面(前面也行)添加onCreateOptionsMenu方法:

        @Override
        public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
            //menu.clear();//不清空就会变成添加进来而不是替换
            inflater.inflate(R.menu.menu_home, menu);
            super.onCreateOptionsMenu(menu, inflater);
        }

说明一下,被注释掉的menu.clear()的作用是在Activity共有的ActionBar中已有组件时为当前fragment清除组件。但由于我前面已经删掉了Activity源码中的解析菜单文件的代码,所以这句话就没必要了。
效果:
Android实战【可可爱爱一零一动植物志】(开发)
另外,我们还可以在fragment的源码中用onOptionItemSelected方法给按钮加动作:

        @Override
        public boolean onOptionsItemSelected(@NonNull MenuItem item) {
            Toast.makeText(getActivity(),"okk",Toast.LENGTH_SHORT).show();
            return super.onOptionsItemSelected(item);
        }

效果就是点击后出现提示“okk”。

- 2020.7.7 登录页面
由于在校学生、教职工用学号/工号登录,而校友需要用手机号登录,所以登录页面选用Android自带的Tabbed Activity。默认状态如下:
Android实战【可可爱爱一零一动植物志】(开发)
可以看到,不同tab下内容是不同的。我原本以为它需要创建两个fragment布局文件,却发现只有一个activity布局文件和一个fragment布局文件。这就很迷了,它怎么做到用一个fragment创建出不同内容的呢?我发现,在这个fragment布局文件中,只有TextView组件,而没有为其添加内容。事实上,文本内容是在PlaceholderFragment类的onCreateView方法中动态添加的:

@Override
    public View onCreateView(
            @NonNull LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.fragment_tabbed_activity_test, container, false);
        final TextView textView = root.findViewById(R.id.section_label);
        pageViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
                textView.setText(s);
            }
        });
        System.out.println("in PlaceholderFragment.onCreateView");
        return root;
    }

但这里面也没有体现文本内容,真正的文本由pageViewModel的getText()方法提供。于是打开PageViewModel文件,发现getText()的返回值mText这样定义的:

    private LiveData<String> mText = Transformations.map(mIndex, new Function<Integer, String>() {
        @Override
        public String apply(Integer input) {
            System.out.println("in PageViewModel.apply:input="+input);
            return "Hello world from section: " + input;
        }
    });

由于没学过Function接口,这代码看的我一脸问号,赶紧去补课函数式编程。看到什么是函数式编程思维?的回答中提到“函数式编程关心数据的映射,命令式编程关心解决问题的步骤”,我似懂非懂;又看了函数式编程入门教程,大概理解了前面回答的意思。我们之前学的Java方法其实不算是“很纯”的函数,比起数值的输入输出,我们其实更关心这个方法“能完成什么工作”。具体一些讲,我们允许有不需要传参的方法,有不需要返回值的方法,这就和狭义的“函数”概念不同;除了处理参数,方法还可以完成许多额外的工作;而且对于静态方法,参数往往参与构建逻辑,而不是作为自变量存在。但函数式编程中的方法,一定是”很纯“的函数,就像知乎回答中说的“关心映射”;或者就可以把它理解为一个运算符。
回到这段代码,这个 new Function<Integer, String>() {…}就是定义了一个从整型到字符串类型的映射,对于每一个输入的整型n,输出“Hello world from section:n”。
了解了内容切换的原理,我就先在fragment的布局文件中写出登录界面大致的样貌,然后如法炮制,让它根据tab在登录方式提示字中呈现“学号/工号登录”或“手机号登录”:

    private String[] accountLabel={"学号/工号登录:","手机号登录:"};
    private MutableLiveData<Integer> mIndex = new MutableLiveData<>();
    private LiveData<String> mText = Transformations.map(mIndex, new Function<Integer, String>() {
        @Override
        public String apply(Integer input) {
            System.out.println("in apply:input="+input);
            return accountLabel[input-1];//不-1就会数组越界
    }
    });

最后效果如下:
Android实战【可可爱爱一零一动植物志】(开发)

本文地址:https://blog.csdn.net/benzenene/article/details/107126595