WEEK5-arkk|Android应用门户界面实现

功能说明

本学期移动开发课程我选择制作一个类似“豆瓣”的应用,为用户提供书影音游的评分与记录,去除了豆瓣中“小组”等冗余社交模块。

⌈arkk⌋取自“诺亚方舟”,寓意留存对用户个人世界有意义的事物/信息。Double the k, double the flavor.

分为五个模块:

  • STREAM-动态流
  • ARCHIVE-书影音游档案
  • NEW-新发布/标记
  • NOTIFICATIONS-通知
  • ME-用户主页

关键源码

JAVA文件

java.jpg

fragment

package com.hzl.arkk;

import android.os.Bundle;

import androidx.fragment.app.Fragment;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;


public class fragArchive extends Fragment {

    public fragArchive() {

    }


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        return inflater.inflate(R.layout.fragment_archive, container, false);
    }
}
复制代码

MainActivity

package com.hzl.arkk;

import androidx.appcompat.app.AppCompatActivity;

import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;

import android.annotation.SuppressLint;
import android.os.Bundle;

import android.view.View;

import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;


public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Fragment[] frags;
    private FragmentManager fragManager;

    private int selected = 0; //当前选中界面
    private int last = 1; //上一选中界面

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初次运行执行
        if (savedInstanceState == null) {
            LinearLayout llStream = findViewById(R.id.llStream);
            LinearLayout llArchive = findViewById(R.id.llArchive);
            Button btnNew = findViewById(R.id.btnNew);
            LinearLayout llDM = findViewById(R.id.llDM);
            LinearLayout llUser = findViewById(R.id.llUser);
            //设置监听
            llStream.setOnClickListener(this);
            llArchive.setOnClickListener(this);
            btnNew.setOnClickListener(this);
            llDM.setOnClickListener(this);
            llUser.setOnClickListener(this);

            initFrags();

            //默认首页
            llStream.performClick();
            this.setTitle("arkk");
        }
    }

    /*fragment初始化*/
    private void initFrags() {
        frags = new Fragment[5];
        fragManager = getSupportFragmentManager();
        FragmentTransaction fragTrans = fragManager.beginTransaction();

        fragStream fragStream = new fragStream();
        fragArchive fragArchive = new fragArchive();
        fragNew fragNew = new fragNew();
        fragDM fragDM = new fragDM();
        fragUser fragUser = new fragUser();

        frags[0] = fragStream;
        frags[1] = fragArchive;
        frags[2] = fragNew;
        frags[3] = fragDM;
        frags[4] = fragUser;
        //初始隐藏所有fragment
        for (Fragment frag : frags) {
            fragTrans.hide(frag);
        }
    }

    /*替换fragment内容*/
    private void switchContent(Fragment former, Fragment latter) {
        FragmentTransaction fragTrans = fragManager.beginTransaction();
        if (former != latter) {
            if (!latter.isAdded()) {
                fragTrans.add(R.id.fragContainer, latter).hide(former).show(latter).commit();
            } else {
                fragTrans.hide(former).show(latter).commit();
            }
        }
    }

    @SuppressLint("NonConstantResourceId")
    @Override
    public void onClick(View view) {
        //上一选中页面焦点颜色恢复
        ImageView imvIcon = null;
        TextView txvTitle = null;
        switch (last) {
            case 0:
                imvIcon = findViewById(R.id.svgStream);
                txvTitle = findViewById(R.id.txtStream);
                break;
            case 1:
                imvIcon = findViewById(R.id.svgArchive);
                txvTitle = findViewById(R.id.txtArchive);
                break;
            case 3:
                imvIcon = findViewById(R.id.svgDM);
                txvTitle = findViewById(R.id.txtDM);
                break;
            case 4:
                imvIcon = findViewById(R.id.svgUser);
                txvTitle = findViewById(R.id.txtUser);
                break;
            default:
                break;
        }
        if (last != 2) { //排除对中间的“新建”button进行操作
            imvIcon.setColorFilter(getColor(R.color.white));
            txvTitle.setTextColor(getColor(R.color.white));
        }
        //确定当前被选中的界面
        switch (view.getId()) {
            case R.id.llStream:
                this.setTitle("STREAM");//更换actionBar的title
                selected = 0;
                break;
            case R.id.llArchive:
                this.setTitle("ARCHIVE");
                selected = 1;
                break;
            case R.id.btnNew: // 选中“新建”button时隐藏actionBar
                getSupportActionBar().hide();
                selected = 2;
                break;
            case R.id.llDM:
                this.setTitle("NOTIFICATIONS");
                selected = 3;
                break;
            case R.id.llUser:
                this.setTitle("ME");
                selected = 4;
                break;
            default:
                break;
        }
        //当前选中非“新建”button,且actionBar被隐藏时恢复显示
        if (selected != 2 && !getSupportActionBar().isShowing()) {
            getSupportActionBar().show();
        }
        //当前选中页面颜色焦点
        switch (selected) {
            case 0:
                imvIcon = findViewById(R.id.svgStream);
                txvTitle = findViewById(R.id.txtStream);
                break;
            case 1:
                imvIcon = findViewById(R.id.svgArchive);
                txvTitle = findViewById(R.id.txtArchive);
                break;
            case 3:
                imvIcon = findViewById(R.id.svgDM);
                txvTitle = findViewById(R.id.txtDM);
                break;
            case 4:
                imvIcon = findViewById(R.id.svgUser);
                txvTitle = findViewById(R.id.txtUser);
                break;
            default:
                break;
        }
        if (selected != 2) {
            imvIcon.setColorFilter(getColor(R.color.purple_500));
            txvTitle.setTextColor(getColor(R.color.purple_700));
        }

        switchContent(frags[last], frags[selected]);

        last = selected;
    }
}
复制代码

RES文件

res.jpg

drawable

drawable.jpg

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="200dp"
    android:height="200dp"
    android:viewportWidth="1024"
    android:viewportHeight="1024">
  <path
      android:pathData="M1024,912a32,32 0,0 1,-32 32L32,944a32,32 0,0 1,-32 -32v-480a31.2,31.2 0,0 1,6.4 -18.02l-0.48,-0.32 224,-320 0.48,0.32A31.36,31.36 0,0 1,256 80h512a31.36,31.36 0,0 1,25.6 13.98l0.48,-0.32 224,320 -0.48,0.32a31.2,31.2 0,0 1,6.4 18.02v480zM64,880h896v-416h-205.89l-86.66,144.48 -0.48,-0.29A31.52,31.52 0,0 1,640 624h-256a31.52,31.52 0,0 1,-26.98 -15.81l-0.45,0.29L269.89,464L64,464v416zM751.33,144L272.67,144l-179.2,256L288,400a31.52,31.52 0,0 1,26.98 15.81l0.48,-0.26 86.66,144.45h219.78l86.69,-144.45 0.45,0.26A31.52,31.52 0,0 1,736 400h194.53z"
      android:fillColor="#FFFFFF"/>
</vector>
复制代码

layout

layout.jpg

activity_main

main_xml.jpg

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/fragContainer"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:orientation="horizontal"
        app:layout_constraintBottom_toTopOf="@+id/naviBar"
        app:layout_constraintEnd_toEndOf="@id/naviBar"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </FrameLayout>

    <LinearLayout
        android:id="@+id/naviBar"
        android:layout_width="match_parent"
        android:layout_height="56dp"
        android:background="@color/purple_200"
        android:orientation="horizontal"
        app:layout_constraintBottom_toBottomOf="parent">

        <LinearLayout
            android:id="@+id/llStream"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:orientation="vertical"
            tools:ignore="UseCompoundDrawables">

            <ImageView
                android:id="@+id/svgStream"
                android:layout_width="24dp"
                android:layout_height="24dp"
                android:layout_gravity="center"
                android:layout_marginTop="8dp"
                app:srcCompat="@drawable/btm_stream" />

            <TextView
                android:id="@+id/txtStream"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_marginBottom="12dp"
                android:text="@string/strStream"
                android:textAlignment="center"
                android:textColor="@color/white"
                android:textSize="11sp" />
        </LinearLayout>

        <Button
            android:id="@+id/btnNew"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginBottom="8dp"
            android:layout_weight="0.7"
            android:foreground="@drawable/btm_new"
            android:foregroundGravity="center"
            android:minHeight="48dp"
            tools:ignore="SpeakableTextPresentCheck,TouchTargetSizeCheck" />

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
复制代码

main_view.jpg

fragment

frag_xml.jpg

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/frmArchive"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".fragArchive">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="@string/strArchive"
        android:textSize="30sp" />
</FrameLayout>
复制代码

frag_view.jpg

values

colors

color.jpg

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="purple_200">#FFBB86FC</color>
</resources>
复制代码
strings

string.jpg

<resources>
    <string name="app_name">arkk</string>
    <string name="strArchive">ARCHIVE</string>
</resources>
复制代码

功能测试

界面切换-图标&标签选中变色

switch-sample.gif

方向切换-解决fragment在方向改变等情况下重叠的问题

landscape-sample.gif

bottomNavigationView控件实现

bnv-sample.gif

核心技术

Fragment

可参见Fragment详解系列,写得非常系统,下面是本次作业中遇到的与Fragment相关的关键点:

Inflate()方法

在写fragment的java文件时,需继承Fragment基类,重写的其中一个回调方法是onCreateView()。如果该Fragment有界面,那么,返回由对应xml文件生成的View;如果该Fragment是没有界面的,返回的是Null。

动态添加Fragment

为触发Fragment变化的点击事件设置监听

setOnClickListener()方法为控件注册一个监听器,点击时就会执行监听器中的onClick()方法,通过触发view的id区分不同的操作。

动态添加Fragment步骤
  1. 获取到FragmentManager,在V4包中通过getSupportFragmentManager(),在系统中原生的Fragment是通过getFragmentManager()获得的。
  2. 开启一个事务,通过FragmentTransaction调用beginTransaction()方法开启。
  3. 向容器内加入Fragment,一般使用add()或者replace()方法实现,需要传入容器的id和Fragment的实例。
  4. 提交事务,调用commit()方法提交。

流程类似数据库的事务操作,每次在使用FragmentTransaction的时候都需要重新获取,每一个FragmentTransaction只能够commit()一次

FragmentManager

管理activity中的fragments

FragmentTransaction

对当前的Fragment进行管理

add(): 向Activity加入一个片段,这个片段在Activity容器中有他自己的视图。

hide(): 隐藏已经存在的Fragment,但是仅仅对已经添加到父容器中的Fragment有关,隐藏Fragment的View

show(): 显示一个以前被隐藏的Fragment,这仅仅对已经添加到Activity中的Fragment有关,显示Fragment的View

detach(): Fragment的视图被销毁,但是它的状态没有被销毁,还是被FragmentManager管理。

attach(): Fragment的view重新加载到UI视图中,并显示,也就是执行onCreateView()→onActivityCreate()→onStart()→onResume()

replace(): 就相当于执行remove(Fragment)→add(int, Fragment, String)这两个方法

与Theme适配的颜色变换

textView和imageView分别使用setTextColor()setColorFilter(),参数用getColor(R.color.Color_name)从res获取values/colors下设置的颜色,以达到与Theme适配的效果。

心得体会

Material Design & bottomNavigationView控件

本次作业中我尝试使用了两种方法实现Navigation Bar,一种是老师在课上演示的用layout控件组合设计,另一种就是官方提供的bottomNavigationView。

  • 使用layout控件时,margin参数参考了官方文档。

portrait-1.jpg

portrait-2.jpg

landscape.jpg

  • bottomNavigationView控件只需要关联对应的Menu.xml,就会自动生成符合Material Design设计理念的Navigation Bar。个人非常喜欢非选中模块仅显示图标,以及选中的点击动画效果,如果有机会的话,未来或许会尝试使用layout实现。

svg格式的优势

svg使用XML格式定义图形,相对于.jpg、.png甚至.webp具有较多优势:

  • 省时间,图像与分辨率无关,适配不同安卓机型的分辨率;
  • 省空间,体积小,一般复杂图像也能做到kb。

基于以上优点,应用的图标均选用svg格式。

gravity和layout_gravity属性的区别

  • gravity:设置自身内部元素的对齐方式。
  • layout_gravity:设置自身相当于父容器的对齐方式。

get和getSupport方法

V4包中需使用getSupport方法,否则会导致应用闪退。

Fragment在方向改变等情况下重叠

在方向、屏幕大小、键盘显隐等状态变化时,正在运行的Activity会重启(先后调用onDestroy()和onCreate()),在销毁之前执行了onSaveInstanceState()方法,保存了Activity的一些信息,其中就包括添加过的Fragment,重启后又对之前保存的Fragment进行了恢复,后续操作的Fragment在其上造成重叠。

解决方法

在AndroidManifest.xml中添加

android: configChanges="orientation|screenSize|keyboardHidden

阻止重启,让 Activity 不销毁也不新建,共用原有布局调用onCreate()方法中的onConfigurationChanged()方法。

Repository

Gitee

  • Gitee控件没有适配最新版本的AS,需要在git bash中生成SSN key,通过repository的HTTPS(如使用SSN push被拒的话)与Gitee建立连接

  • 如JetBrains网页登录授权GitHub账号失败,在settings的GitHub板块下按alt+Insert,选择Log In with Token

猜你喜欢

转载自juejin.im/post/7016914044627976222