功能说明
本学期移动开发课程我选择制作一个类似“豆瓣”的应用,为用户提供书影音游的评分与记录,去除了豆瓣中“小组”等冗余社交模块。
⌈arkk⌋取自“诺亚方舟”,寓意留存对用户个人世界有意义的事物/信息。Double the k, double the flavor.
分为五个模块:
- STREAM-动态流
- ARCHIVE-书影音游档案
- NEW-新发布/标记
- NOTIFICATIONS-通知
- ME-用户主页
关键源码
JAVA文件
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文件
drawable
<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
activity_main
<?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>
复制代码
fragment
<?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>
复制代码
values
colors
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
</resources>
复制代码
strings
<resources>
<string name="app_name">arkk</string>
<string name="strArchive">ARCHIVE</string>
</resources>
复制代码
功能测试
界面切换-图标&标签选中变色
方向切换-解决fragment在方向改变等情况下重叠的问题
bottomNavigationView控件实现
核心技术
Fragment
可参见Fragment详解系列,写得非常系统,下面是本次作业中遇到的与Fragment相关的关键点:
Inflate()方法
在写fragment的java文件时,需继承Fragment基类,重写的其中一个回调方法是onCreateView()。如果该Fragment有界面,那么,返回由对应xml文件生成的View;如果该Fragment是没有界面的,返回的是Null。
动态添加Fragment
为触发Fragment变化的点击事件设置监听
setOnClickListener()方法为控件注册一个监听器,点击时就会执行监听器中的onClick()方法,通过触发view的id区分不同的操作。
动态添加Fragment步骤
- 获取到FragmentManager,在V4包中通过getSupportFragmentManager(),在系统中原生的Fragment是通过getFragmentManager()获得的。
- 开启一个事务,通过FragmentTransaction调用beginTransaction()方法开启。
- 向容器内加入Fragment,一般使用add()或者replace()方法实现,需要传入容器的id和Fragment的实例。
- 提交事务,调用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参数参考了官方文档。
- 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控件没有适配最新版本的AS,需要在git bash中生成SSN key,通过repository的HTTPS(如使用SSN push被拒的话)与Gitee建立连接
-
如JetBrains网页登录授权GitHub账号失败,在settings的GitHub板块下按alt+Insert,选择Log In with Token