Android Studio 智慧城市项目开发过程记录分享
Ctrl+Alt+L
选择一个字段使用快捷键可以打开字段源文件
Ctrl+B
这种换行不需要特意在行末添加换行
shift+enter
ctrl+alt+enter
如果分号下一行已经是空行则不会添加而是转到那一行
ctrl+shift+enter
按住alt+上下方向键
按住alt+左右方向键
ctrl+左右方向键
ctrl+上下方向键
Ctrl+D
Ctrl+E
快捷使用一些常用代码模板,try catch等
Ctrl+Alt+T
Ctrl+I
选中字段后使用Ctrl+Shift+U
输入自定义的代码模板或自带的代码缩写后按tab自动补全
如psf
ctrl+D 快速复制单行代码
ctrl+W 选中一个字段
ALT+J 选中字段后快速选择同名字段
ctrl+左右方向键快速跳过字段
shift+方向键选择字段
选中代码后按着shift+alt +上下方向键移动这段代码
Ctrl+W,多次使用Ctrl+W可扩大选择范围
同几行内可使用鼠标中键选中
选中不同部分的相同代码可以先选中一个字段,然后按住Alt+J往后依次选中相同代码。
首先新建项目
Git使用之后再复盘
配置好Git目录后为git设置账号邮箱(用于区分提交者)
xxxxxxxxxx
21git config --global user.name name
2git config --global user.email email
直接win+r -> cmd 打开命令行设置
引入开发资料中的插件
Android studio File->Settings..->Plugins -->install plugin from disk..导入 GsonFormat.jar ,
重启 android studio .
默认配置好sdk与虚拟机
打开虚拟机,默认配置为sdk8.0
引入开发使用的依赖包
通常使用的有
banner 轮播图
okhttp 网络请求
MPAndroidChart 绘制图表
Gson 格式化实例数据
GsonFormatPlush 通过json生成实例的工具
glide 图片加载库
将需要的依赖jar,arr包放入项目下的libs文件夹后再app目录下的build.gradle中编写如下代码
在dependencies闭包下添加aar
xxxxxxxxxx
11implementation fileTree(dir: "libs", include: ["*.jar","*.aar"])
引入依赖
在android闭包下添加buildFeatures闭包并开启viewBinding开启控件绑定
x1
2 buildFeatures{
3 viewBinding=true
4 }
这里构建工具版本过高会报错,修改为30.0.0
即可
xxxxxxxxxx
11buildToolsVersion "30.0.0"
引入依赖包一般引入新版本,根据api需要还应引入一些依赖包
xxxxxxxxxx
21
2implementation 'com.google.android.material:material:1.1.0'
然后配置清单文件就正式编写代码,这里需要添加上网络权限和根据要求设置
在manifest下与application同级处添加网络请求权限,点击label处的引用@string/app_name,使用快捷键ctrl+b可直接打开strings.xml文件来修改相应引用文字
xxxxxxxxxx
11<uses-permission android:name="android.permission.INTERNET"/>
如果设置了目标sdk版本大于28(9.0)还需要在application下设置运行使用明文流量(http)否则只能执行https请求,但软件默认sdk是8.0(26),使用明文请求的指令是默认为true的
xxxxxxxxxx
31<application
2 android:usesCleartextTraffic="true">
3</application>
软件名称
在主页面MainActivity添加键盘模式防止输入框顶起布局
xxxxxxxxxx
11 android:windowSoftInputMode="adjustPan|stateHidden"
编写功能实现前需要使用的基类,通用布局等共用代码
引入设计素材,基础样式,通用的布局代码,控件如toolbar等
如常用的引导图,图标等
这里如果没有设计素材就只能从api中找到获取轮播图的接口了
这里我测试了服务器没有引导页的轮播图,只好从以前的设计素材中获取
编写一个通用的toolbar,此处顺便从设计素材中引入了返回图标
xxxxxxxxxx
281
2<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="match_parent"
4 android:layout_height="?android:actionBarSize"
5 android:background="@color/colorPrimaryDark">
6 <ImageView
7 android:layout_width="24dp"
8 android:layout_height="24dp"
9 android:src="@drawable/back"
10 android:layout_centerVertical="true"
11 android:layout_marginStart="16dp"
12 android:visibility="gone"
13 android:id="@+id/back"
14 />
15 <TextView
16 android:layout_width="wrap_content"
17 android:layout_height="wrap_content"
18 android:textColor="@android:color/white"
19 android:text="@string/app_name"
20 android:textSize="22sp"
21 android:maxEms="8"
22 android:maxLines="1"
23 android:ellipsize="end"
24 android:id="@+id/title"
25 android:layout_centerInParent="true"
26 />
27
28</RelativeLayout>
开启ViewBinding后将一些控件绑定等页面常用事件封装到抽象基类如BaseActivity,BaseFragment,BaseAdapter分别是活动,碎片,适配器的基类,封装Adapter需要的ViewHolder,网络请求的OkHttps工具类以及存储常用常量的Const类
封装需要使用的常量,如接口的服务器地址,用于初始化SP(轻量读写数据)的变量名,以及读写变量时的变量名,这里只需要读写TOKEN和判断初次登录的FIRST等变量, 顺便准备将TOKEN值存在这里使token只在登录登出时读写赋值
xxxxxxxxxx
131
2public class Const {
3 public static final String SP = "SP";
4 public static final String TOKEN = "TOKEN";
5 public static final String FIRST = "FIRST";
6 public static final String IP = "IP";
7 public static final String PORT = "PORT";
8 public static String SEARCH = "";
9 public static String URL = "http://218.7.112.123:10001";
10 public static String TOKEN_VAL = "";
11
12}
13
*注:代码中包含一些可以优化的部分,比赛为了快速。一般不会特意处理不严重影响的问题
这里使用了lambda表达式,AS4.0在输入lambda表达式后会提示设置jdk版本
直接设置Source 和Target的java版本为1.8就可以
也可以手动在app下的build.gradle为android闭包添加如下代码
使用静态引用是引用需要引用对象内的静态变量,使用的时候可以省去前缀
xxxxxxxxxx
41compileOptions {
2 targetCompatibility JavaVersion.VERSION_1_8
3 sourceCompatibility JavaVersion.VERSION_1_8
4}
继承活动初始化时使用反射获取ViewBinding,设置布局,暴露初始化接口,初始化SP 按需求封装Toast, 读写SP,切换Fragment等方法 注意要暴露的方法和变量需要加上protected
xxxxxxxxxx
551
2import android.content.Intent;
3import android.content.SharedPreferences;
4import android.os.Bundle;
5import android.view.LayoutInflater;
6import android.widget.Toast;
7
8import androidx.annotation.Nullable;
9import androidx.appcompat.app.AppCompatActivity;
10import androidx.fragment.app.Fragment;
11import androidx.viewbinding.ViewBinding;
12
13import java.lang.reflect.Method;
14import java.lang.reflect.ParameterizedType;
15
16public abstract class BaseActivity<T extends ViewBinding> extends AppCompatActivity {
17 protected T binding;
18 protected SharedPreferences sp;
19
20 protected void onCreate( Bundle savedInstanceState) {
21 super.onCreate(savedInstanceState);
22 ParameterizedType type = (ParameterizedType)getClass().getGenericSuperclass();
23 try {
24 Class<?> clz = (Class<?>)type.getActualTypeArguments()[0];
25 Method method = clz.getDeclaredMethod("inflate", LayoutInflater.class);
26 binding = (T)method.invoke(null, getLayoutInflater());
27 } catch (Exception e) {
28 e.printStackTrace();
29 }
30 setContentView(binding.getRoot());
31 sp = getSharedPreferences(Const.SP,0);
32 init();
33
34 }
35 protected abstract void init();
36 protected void jump(Class target){
37 startActivity(new Intent(this,target));
38 }
39 protected void toast(String msg){
40 runOnUiThread(()->Toast.makeText(BaseActivity.this, msg, Toast.LENGTH_SHORT).show());
41 }
42 protected void setSp(String key, String val){
43 sp.edit().putString(key, val).apply();
44 }
45 protected String getSP(String key){
46 return sp.getString(key, "");
47 }
48 protected void switchFragment(Fragment fragment, int viewId){
49 getSupportFragmentManager()
50 .beginTransaction()
51 .replace(viewId, fragment)
52 .commit();
53 }
54}
55
这里也可以保留了一个Activity引用,但一般不会这么做,因为其不同于requireActivity()返回的弱引用,这属于强引用使垃圾回收GC无法回收Activity从而容易崩溃,requireActivity()是判空返回底层类的Activity,不同于当前显示活动直接持有Activity (大概)
xxxxxxxxxx
611
2import android.content.Intent;
3import android.content.SharedPreferences;
4import android.os.Bundle;
5import android.view.LayoutInflater;
6import android.view.View;
7import android.view.ViewGroup;
8import android.widget.Toast;
9
10import androidx.annotation.NonNull;
11import androidx.annotation.Nullable;
12import androidx.fragment.app.Fragment;
13import androidx.viewbinding.ViewBinding;
14
15import java.lang.reflect.Method;
16import java.lang.reflect.ParameterizedType;
17
18
19public abstract class BaseFragment<T extends ViewBinding> extends Fragment {
20 protected T binding;
21 protected SharedPreferences sp;
22
23
24
25 public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
26 ParameterizedType type = (ParameterizedType)getClass().getGenericSuperclass();
27 try{
28 Class<?> clz = (Class<?>)type.getActualTypeArguments()[0];
29 Method method = clz.getDeclaredMethod("inflate", LayoutInflater.class);
30 binding = (T) method.invoke(null,getLayoutInflater());
31 }catch (Exception e){
32 e.printStackTrace();
33 }
34 return binding.getRoot();
35 }
36
37
38 public void onViewCreated( View view, Bundle savedInstanceState) {
39 super.onViewCreated(view, savedInstanceState);
40 sp = requireActivity().getSharedPreferences(Const.SP, 0);
41 init();
42 }
43
44 protected abstract void init();
45 protected void toast(String msg){
46 requireActivity().runOnUiThread(()-> Toast.makeText(requireActivity(), msg, Toast.LENGTH_SHORT).show());
47 }
48 protected void jump(Class target){
49 startActivity(new Intent(requireActivity(),target));
50 }
51 protected void runOnUi(Runnable run){
52 requireActivity().runOnUiThread(run);
53 }
54 protected void setSp(String key, String val){
55 sp.edit().putString(key, val).apply();
56 }
57 protected String getSP(String key){
58 return sp.getString(key, "");
59 }
60}
61
xxxxxxxxxx
201
2import android.content.Context;
3
4import androidx.recyclerview.widget.RecyclerView;
5import androidx.viewbinding.ViewBinding;
6
7
8public abstract class BaseViewHolder<T, V extends ViewBinding> extends RecyclerView.ViewHolder {
9 protected V binding;
10 protected Context context;
11
12 public BaseViewHolder(V binding){
13 super(binding.getRoot());
14 this.binding = binding;
15 this.context = binding.getRoot().getContext();
16 }
17
18 protected abstract void refresh(int pos, T bean);
19}
20
xxxxxxxxxx
351
2import android.content.Context;
3
4import androidx.annotation.NonNull;
5import androidx.recyclerview.widget.RecyclerView;
6import androidx.viewbinding.ViewBinding;
7
8import java.util.List;
9
10public abstract class BaseAdapter<T, V extends ViewBinding> extends RecyclerView.Adapter<BaseViewHolder> {
11 protected Context context;
12 protected List<T> list;
13 protected V binding;
14
15 public BaseAdapter(Context context, List<T> list){
16 this.context = context;
17 this.list = list;
18 }
19
20 public void setData(List<T> list) {
21 this.list = new ArrayList<>(list);
22 notifyDataSetChanged();
23 }
24
25
26 public void onBindViewHolder( BaseViewHolder holder, int position) {
27 holder.refresh(position, list.get(position));
28 }
29
30
31 public int getItemCount() {
32 return list!=null?list.size():0;
33 }
34}
35
使用BaseAdapter ViewHolder 的例子
xxxxxxxxxx
381
2import android.content.Context;
3import android.view.LayoutInflater;
4import android.view.ViewGroup;
5
6import androidx.annotation.NonNull;
7
8import com.example.practice0924.base.BaseAdapter;
9import com.example.practice0924.base.BaseViewHolder;
10import com.example.practice0924.databinding.ItemServeBinding;
11import com.example.practice0924.pojo.Serve;
12
13import java.util.List;
14
15public class ServeAdapter extends BaseAdapter<Serve.RowsBean, ItemServeBinding> {
16
17 public ServeAdapter(Context context, List<Serve.RowsBean> list){
18 super(context, list);
19 }
20
21
22
23 public BaseViewHolder<Serve.RowsBean, ItemServeBinding> onCreateViewHolder( ViewGroup parent, int viewType) {
24 return new ServeViewHolder(ItemServeBinding.inflate(LayoutInflater.from(context), parent, false));
25 }
26 class ServeViewHolder extends BaseViewHolder<Serve.RowsBean, ItemServeBinding>{
27
28 public ServeViewHolder(ItemServeBinding binding){
29 super(binding);
30 }
31
32
33 protected void refresh(int pos, Serve.RowsBean bean) {
34
35 }
36 }
37}
38
一个实现Callback的抽象类,可以直接返回网络请求结果和相关实体类,因为获取class的反射代码都是一套,所以实际代码不会太多
xxxxxxxxxx
371
2import com.google.gson.Gson;
3
4import org.jetbrains.annotations.NotNull;
5
6import java.io.IOException;
7import java.lang.reflect.ParameterizedType;
8
9import okhttp3.Call;
10import okhttp3.Callback;
11import okhttp3.Response;
12
13public abstract class Callbacks<T> implements Callback {
14
15
16 public void onFailure( Call call, IOException e) {
17 e.printStackTrace();
18 }
19
20 public void onResponse( Call call, Response response) throws IOException {
21 String res = response.body().string();
22 getRes(res, getBean(res));
23 }
24 protected abstract void getRes(String res, T bean);
25 private T getBean(String json){
26 T res = null;
27 ParameterizedType type = (ParameterizedType)getClass().getGenericSuperclass();
28 try{
29 Class<?> clz = (Class<?>)type.getActualTypeArguments()[0];
30 res = (T)new Gson().fromJson(json,clz);
31 }catch (Exception e){
32 e.printStackTrace();
33 }
34 return res;
35 }
36}
37
xxxxxxxxxx
551
2import org.json.JSONObject;
3
4import java.util.HashMap;
5
6import okhttp3.MediaType;
7import okhttp3.OkHttpClient;
8import okhttp3.Request;
9import okhttp3.RequestBody;
10
11import static com.example.practice0924.base.Const.*;
12
13public class OKHttps {
14 private static final OkHttpClient client = new OkHttpClient();
15
16 public static void get(String url, Callbacks callback) {
17 Request request = new Request.Builder()
18 .url(URL + url)
19 .addHeader("Authorization", TOKEN_VAL)
20 .build();
21 client.newCall(request).enqueue(callback);
22 }
23
24 public static void post(String url, HashMap<String, String> map, Callbacks callback) {
25 JSONObject json = new JSONObject(map);
26
27 RequestBody body = RequestBody.create(json.toString(),
28 MediaType.parse("application/json;charset=UTF-8"));
29
30 Request request = new Request.Builder()
31 .url(URL + url)
32 .post(body)
33 .addHeader("Authorization", TOKEN_VAL)
34 .build();
35
36 client.newCall(request).enqueue(callback);
37
38 }
39 public static void put(String url, HashMap<String, String> map, Callbacks callback) {
40 JSONObject json = new JSONObject(map);
41
42 RequestBody body = RequestBody.create(json.toString(),
43 MediaType.parse("application/json;charset=UTF-8"));
44
45 Request request = new Request.Builder()
46 .url(URL + url)
47 .put(body)
48 .addHeader("Authorization", TOKEN_VAL)
49 .build();
50
51 client.newCall(request).enqueue(callback);
52
53 }
54
55}
用于占位给各种入口提供一个空白入口的Activity
xxxxxxxxxx
141
2<LinearLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 android:orientation="vertical"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:background="#eeeeee"
8 >
9
10 <include
11 android:id="@+id/toolbar"
12 layout="@layout/toolbar" />
13
14</LinearLayout>
跳转到EmptyActivity在intent中传入title即可
xxxxxxxxxx
191
2import android.text.TextUtils;
3import android.view.View;
4
5import com.example.practice0924.databinding.ActivityEmptyBinding;
6
7public class EmptyActivity extends BaseActivity<ActivityEmptyBinding> {
8
9
10 protected void init() {
11 String title = getIntent().getStringExtra("title");
12
13 if(!TextUtils.isEmpty(title)){
14 binding.toolbar.title.setText(title);
15 }
16 binding.toolbar.back.setVisibility(View.VISIBLE);
17 binding.toolbar.back.setOnClickListener(v->finish());
18 }
19}
使用方法
xxxxxxxxxx
31Intent intent = new Intent(this),target);
2intent.putExtra("title",title);
3startActivity(intent);
为styles添加冷启动开屏样式,将默认样式的parent修改为无actionbar样式
修改parent后复制一份设置windowBackground为第一张引导图即可
主样式继承MaterialComponent 是为了方便个人中心的按钮使用边框属性
xxxxxxxxxx
121<resources>
2 <style name="SplashTheme" parent="Theme.AppCompat.Light.NoActionBar">
3 <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
4 <item name="android:windowBackground">@drawable/introa</item>
5 </style>
6 <style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
7 <item name="colorPrimary">@color/colorPrimary</item>
8 <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
9 <item name="colorAccent">@color/colorAccent</item>
10 </style>
11
12</resources>
新建引导页活动勾选启动类
在清单内将启动类设置为SplashActivty,设置样式为SplashTheme
接着编写布局
xxxxxxxxxx
301
2<RelativeLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 android:layout_width="match_parent"
5 android:layout_height="match_parent">
6 <com.youth.banner.Banner
7 android:layout_width="match_parent"
8 android:layout_height="match_parent"
9 android:id="@+id/banner"/>
10 <Button
11 android:layout_width="wrap_content"
12 android:layout_height="wrap_content"
13 android:text="进入主页"
14 android:layout_centerHorizontal="true"
15 android:layout_alignParentBottom="true"
16 android:layout_marginBottom="72dp"
17 android:visibility="gone"
18 android:id="@+id/enter"
19 />
20 <Button
21 android:layout_width="wrap_content"
22 android:layout_height="wrap_content"
23 android:text="网络设置"
24 android:layout_alignParentEnd="true"
25 android:layout_marginTop="16dp"
26 android:layout_marginEnd="16dp"
27 android:visibility="gone"
28 android:id="@+id/setting"
29 />
30</RelativeLayout>
然后编写SplashActivity代码
逻辑为先继承BaseActivity,
获取sp存储token
判断sp是否初次登录
决定是显示引导页还是直接跳转主页
注意跳转后要关闭这个活动,并且初次登录值的修改和url的拼接将在主页执行
xxxxxxxxxx
1201
2import android.app.AlertDialog;
3import android.net.Uri;
4import android.text.TextUtils;
5import android.view.View;
6import android.widget.EditText;
7import android.widget.LinearLayout;
8
9import com.bumptech.glide.Glide;
10import com.example.practice0924.MainActivity;
11import com.example.practice0924.R;
12import com.example.practice0924.base.BaseActivity;
13import com.example.practice0924.databinding.ActivitySplashBinding;
14import com.youth.banner.adapter.BannerImageAdapter;
15import com.youth.banner.holder.BannerImageHolder;
16import com.youth.banner.indicator.CircleIndicator;
17import com.youth.banner.listener.OnPageChangeListener;
18
19import java.util.Arrays;
20import java.util.List;
21
22import static com.example.practice0924.base.Const.*;
23
24public class SplashActivity extends BaseActivity<ActivitySplashBinding> {
25
26
27 protected void init() {
28
29 TOKEN_VAL = getSP(TOKEN);
30
31
32 if (getSP(FIRST).isEmpty()) {
33
34 List<Integer> imgs = Arrays.asList(
35 R.drawable.introa,
36 R.drawable.introb,
37 R.drawable.introc,
38 R.drawable.introd,
39 R.drawable.introe
40 );
41 binding.banner.setAdapter(new BannerImageAdapter<Integer>(imgs) {
42
43 public void onBindView(BannerImageHolder bannerImageHolder, Integer integer, int i, int i1) {
44 Glide.with(SplashActivity.this).load(integer).into(bannerImageHolder.imageView);
45 }
46 }, false)
47 .setIndicator(new CircleIndicator(this))
48 .addOnPageChangeListener(new OnPageChangeListener() {
49
50 public void onPageScrolled(int i, float v, int i1) {
51 }
52
53
54 public void onPageScrollStateChanged(int i) {
55 }
56
57
58 public void onPageSelected(int i) {
59 if (i == imgs.size() - 1) {
60 binding.enter.setVisibility(View.VISIBLE);
61 binding.setting.setVisibility(View.VISIBLE);
62 } else {
63 binding.enter.setVisibility(View.GONE);
64 binding.setting.setVisibility(View.GONE);
65 }
66 }
67 });
68
69 binding.enter.setOnClickListener(v -> {
70 String h = getSP(IP);
71 String p = getSP(PORT);
72 if (!TextUtils.isEmpty(h.trim()) && !TextUtils.isEmpty(p.trim())) {
73 jump(MainActivity.class);
74 finish();
75 } else {
76 toast("未设置网络");
77 }
78 });
79
80 binding.setting.setOnClickListener(v -> {
81
82 LinearLayout layout = new LinearLayout(this);
83 EditText host = new EditText(this);
84 EditText port = new EditText(this);
85
86 host.setHint("输入主机地址如192.168.1.1");
87 port.setHint("输入端口号如10001");
88
89 host.setText(getSP(IP));
90 port.setText(getSP(PORT));
91
92 layout.setOrientation(LinearLayout.VERTICAL);
93 layout.addView(host);
94 layout.addView(port);
95
96 new AlertDialog.Builder(this)
97 .setTitle("网络设置")
98 .setView(layout)
99 .setNegativeButton("关闭", null)
100 .setPositiveButton("保存", (dialog, which) -> {
101 String h = host.getText().toString();
102 String p = port.getText().toString();
103
104 if (!TextUtils.isEmpty(h.trim()) && !TextUtils.isEmpty(p.trim())) {
105 setSp(IP, h);
106 setSp(PORT, p);
107 toast("保存成功");
108 } else {
109 toast("输入内容为空");
110 }
111 })
112 .create()
113 .show();
114 });
115 } else {
116 jump(MainActivity.class);
117 finish();
118 }
119 }
120}
先编写布局代码
简单的线性布局用于承载用于显示的碎片/片段
这里直接将显示的底部item用xml的方式写了进去
注意tabItem设置id的话ViewBinding会报错
xxxxxxxxxx
561
2<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 android:layout_width="match_parent"
5 android:layout_height="match_parent"
6 android:background="#eeeeee"
7 android:orientation="vertical">
8
9 <include
10 android:id="@+id/toolbar"
11 layout="@layout/toolbar" />
12
13 <FrameLayout
14 android:id="@+id/frameLayout"
15 android:layout_width="match_parent"
16 android:layout_height="0dp"
17 android:layout_weight="1" />
18
19 <com.google.android.material.tabs.TabLayout
20 android:id="@+id/tabLayout"
21 android:layout_width="match_parent"
22 android:layout_height="48dp"
23 android:background="#fff"
24 app:tabMode="auto">
25
26 <com.google.android.material.tabs.TabItem
27 android:layout_width="wrap_content"
28 android:layout_height="wrap_content"
29 android:icon="@drawable/home"
30 android:text="首页" />
31
32 <com.google.android.material.tabs.TabItem
33 android:layout_width="wrap_content"
34 android:layout_height="wrap_content"
35 android:icon="@drawable/allserve"
36 android:text="全部服务" />
37
38 <com.google.android.material.tabs.TabItem
39 android:layout_width="wrap_content"
40 android:layout_height="wrap_content"
41 android:icon="@drawable/restrict"
42 android:text="智慧党建" />
43
44 <com.google.android.material.tabs.TabItem
45 android:layout_width="wrap_content"
46 android:layout_height="wrap_content"
47 android:icon="@drawable/news"
48 android:text="新闻" />
49
50 <com.google.android.material.tabs.TabItem
51 android:layout_width="wrap_content"
52 android:layout_height="wrap_content"
53 android:icon="@drawable/per"
54 android:text="个人中心" />
55 </com.google.android.material.tabs.TabLayout>
56</LinearLayout>
然后编写java 界面,先新建几个空Fragment(因为不需要注册到清单中,可以新建一个Fragment之后复制粘贴几个,注意xml和java对应)
新建相应布局后直接新建Fragment类
xxxxxxxxxx
111
2import com.example.practice0924.base.BaseFragment;
3import com.example.practice0924.databinding.FragmentHomeBinding;
4
5public class HomeFragment extends BaseFragment<FragmentHomeBinding> {
6
7 protected void init() {
8
9 }
10}
11
在Activity内新建Framgnet的实例设置为当前FrameLayout显示,tablayout添加点击事件切换Fragment
暴露一个切换到全部服务的方法给页面中间服务列表的更多服务使用
xxxxxxxxxx
751
2
3
4import android.util.Log;
5import android.widget.Toast;
6
7import com.example.practice0924.base.BaseActivity;
8import com.example.practice0924.databinding.ActivityMainBinding;
9import com.example.practice0924.page.HomeFragment;
10import com.example.practice0924.page.NewsFragment;
11import com.example.practice0924.page.PerFragment;
12import com.example.practice0924.page.RestrictFragment;
13import com.example.practice0924.page.ServeFragment;
14import com.google.android.material.tabs.TabLayout;
15
16import static com.example.practice0924.base.Const.*;
17
18public class MainActivity extends BaseActivity<ActivityMainBinding> {
19
20
21 protected void init() {
22
23
24 URL = String.format("http://%s:%s", getSP(IP), getSP(PORT));
25
26 if (getSP(FIRST).isEmpty()) {
27 setSp(FIRST,"1");
28 }
29
30 binding.toolbar.title.setText("首页");
31 int frameLayoutId = binding.frameLayout.getId();
32 switchFragment(new HomeFragment(), frameLayoutId);
33
34 binding.tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
35
36 public void onTabSelected(TabLayout.Tab tab) {
37 String title = tab.getText().toString();
38 switch(title){
39 case "首页":
40 switchFragment(new HomeFragment(), frameLayoutId);
41 break;
42 case "全部服务":
43 switchFragment(new ServeFragment(), frameLayoutId);
44 break;
45 case "智慧党建":
46 switchFragment(new RestrictFragment(), frameLayoutId);
47 break;
48 case "新闻":
49 switchFragment(new NewsFragment(), frameLayoutId);
50 break;
51 case "个人中心":
52 switchFragment(new PerFragment(), frameLayoutId);
53 break;
54 }
55 binding.toolbar.title.setText(title);
56 }
57
58
59 public void onTabUnselected(TabLayout.Tab tab) {
60
61 }
62
63
64 public void onTabReselected(TabLayout.Tab tab) {
65
66 }
67 });
68 }
69 public void moreServe(){
70 TabLayout.Tab tab = binding.tabLayout.getTabAt(1);
71 tab.select();
72 switchFragment(new ServeFragment(), binding.frameLayout.getId());
73 binding.toolbar.title.setText(tab.getText());
74 }
75}
这里开始编写主页页面
从上至下一个搜索框,一个轮播图,一个服务列表,一个热门新闻列表,一个滑动的新闻分类tabLayout控制最底下的新闻列表
xxxxxxxxxx
851
2<LinearLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 android:orientation="vertical"
6 android:layout_width="match_parent"
7 android:layout_height="match_parent"
8 android:background="#eeeeee"
9 >
10 <androidx.core.widget.NestedScrollView
11 android:layout_width="match_parent"
12 android:layout_height="wrap_content">
13
14 <LinearLayout
15 android:layout_width="match_parent"
16 android:layout_height="wrap_content"
17 android:orientation="vertical">
18 <androidx.cardview.widget.CardView
19 android:layout_width="match_parent"
20 android:layout_height="wrap_content"
21 android:layout_margin="10dp"
22 app:cardCornerRadius="24dp"
23 android:background="#fff"
24 >
25 <androidx.appcompat.widget.SearchView
26 android:id="@+id/searchView"
27 app:queryHint="新闻搜索"
28 android:layout_width="match_parent"
29 android:layout_height="match_parent"/>
30
31 </androidx.cardview.widget.CardView>
32
33 <androidx.cardview.widget.CardView
34 android:layout_width="match_parent"
35 android:layout_height="wrap_content"
36 android:layout_margin="10dp"
37 app:cardCornerRadius="16dp"
38 android:background="#fff"
39 >
40 <com.youth.banner.Banner
41 android:layout_width="match_parent"
42 android:layout_height="150dp"
43 android:id="@+id/banner"/>
44
45 </androidx.cardview.widget.CardView>
46
47 <TextView
48 android:layout_width="match_parent"
49 android:layout_height="wrap_content"
50 android:text="服务入口"
51 android:textSize="17sp"
52 android:layout_marginLeft="10dp"
53 />
54 <androidx.recyclerview.widget.RecyclerView
55 android:layout_width="match_parent"
56 android:layout_height="wrap_content"
57 android:layout_margin="10dp"
58 android:id="@+id/serveEnterRv"/>
59 <TextView
60 android:layout_width="match_parent"
61 android:layout_height="wrap_content"
62 android:text="热门主题"
63 android:textSize="17sp"
64 android:layout_marginLeft="10dp"
65 />
66 <androidx.recyclerview.widget.RecyclerView
67 android:layout_width="match_parent"
68 android:layout_height="wrap_content"
69 android:layout_margin="10dp"
70 android:id="@+id/hotTopicRv"/>
71
72 <com.google.android.material.tabs.TabLayout
73 android:layout_width="match_parent"
74 android:layout_height="wrap_content"
75 app:tabMode="auto"
76 android:id="@+id/newsTypeTl"/>
77
78 <androidx.recyclerview.widget.RecyclerView
79 android:layout_width="match_parent"
80 android:layout_height="wrap_content"
81 android:id="@+id/newsRv"/>
82 </LinearLayout>
83
84 </androidx.core.widget.NestedScrollView>
85</LinearLayout>
需要实现新闻搜索(将输入内容传到另一个Activity显示匹配搜索结果的列表)
将获取的主页轮播图资源设置到轮播图,需要测试接口生成相应pojo类,轮播图点击事件需要相应的设置新闻详情页面可跳转
获取全部服务,设置相应的服务列表item,定义相应适配器
获取热门主题,设置相应新闻列表item,定义相应适配器
获取新闻分类,添加到分类tabLayout
获取默认分类的新闻,设置相应新闻列表item,定义相应适配器
这里先不管新闻搜索列表页面具体实现,只生成一个空Activity用于跳转
xxxxxxxxxx
141
2<LinearLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 android:orientation="vertical"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:background="#eeeeee"
8 >
9
10 <include
11 android:id="@+id/toolbar"
12 layout="@layout/toolbar" />
13
14</LinearLayout>
默认的空界面布局
新建完Activity记得添加到清单中
xxxxxxxxxx
31<activity android:name=".MainActivity"/>
2<activity android:name=".page.NewsSearchActivity"/>
3<activity android:name=".page.NewsDetailActivity"/>
xxxxxxxxxx
151
2import com.example.practice0924.base.BaseActivity;
3import com.example.practice0924.base.Const;
4import com.example.practice0924.databinding.ActivityNewsSearchBinding;
5
6public class NewsSearchActivity extends BaseActivity<ActivityNewsSearchBinding> {
7
8
9 protected void init() {
10 binding.toolbar.title.setText(String.format("%s搜索结果", Const.SEARCH));
11
12 binding.toolbar.back.setVisibility(View.VISIBLE);
13 binding.toolbar.back.setOnClickListener(v->finish());
14 }
15}
首页Fragment实现搜索
xxxxxxxxxx
141 binding.searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
2
3 public boolean onQueryTextSubmit(String query) {
4 Const.SEARCH = query;
5 jump(NewsSearchActivity.class);
6 binding.searchView.setIconified(true);
7 return true;
8 }
9
10
11 public boolean onQueryTextChange(String newText) {
12 return false;
13 }
14 });
先测试轮播图接口
将返回json复制,使用alt+s 将json生成pojo类的字段和方法
跟上一步一样生成一个新闻详情页
调用get请求设置banner图片和点击事件
xxxxxxxxxx
271 OKHttps.get("/prod-api/api/rotation/list?type=2", new Callbacks<HomeBanner>() {
2
3
4 protected void getRes(String res, HomeBanner bean) {
5 runOnUi(() -> {
6 List<HomeBanner.RowsBean> beans = bean.getRows();
7 binding.banner.setAdapter(new BannerImageAdapter<HomeBanner.RowsBean>(beans) {
8
9 public void onBindView(BannerImageHolder bannerImageHolder,
10 HomeBanner.RowsBean rowsBean, int i, int i1) {
11
12 Glide.with(requireActivity()).load(Const.URL + rowsBean.getAdvImg())
13 .into(bannerImageHolder.imageView);
14
15 bannerImageHolder.imageView.setOnClickListener(v -> {
16
17 Intent intent = new Intent(requireActivity(), NewsDetailActivity.class);
18 intent.putExtra("id", rowsBean.getTargetId() + "");
19 startActivity(intent);
20
21 });
22
23 }
24 });
25 });
26 }
27 });
引入需要显示自定义的更多服务图标
使用RecyclerView需要定义显示的item布局,布局管理器,适配器和相应的bean pojo类
先定义item布局
xxxxxxxxxx
211
2<LinearLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 android:layout_width="match_parent"
5 android:layout_height="wrap_content"
6 android:orientation="vertical">
7 <ImageView
8 android:layout_width="48dp"
9 android:layout_height="48dp"
10 android:scaleType="fitXY"
11 android:layout_gravity="center_horizontal"
12 android:src="@drawable/more_serve"
13 android:id="@+id/icon"/>
14 <TextView
15 android:layout_width="wrap_content"
16 android:layout_height="wrap_content"
17 android:layout_gravity="center_horizontal"
18 android:text="icon"
19 android:id="@+id/title"
20 />
21</LinearLayout>
测试接口生成pojo,编写适配器
xxxxxxxxxx
611
2import android.content.Context;
3import android.content.Intent;
4import android.view.LayoutInflater;
5import android.view.ViewGroup;
6
7import androidx.annotation.NonNull;
8
9import com.bumptech.glide.Glide;
10import com.example.practice0924.MainActivity;
11import com.example.practice0924.R;
12import com.example.practice0924.base.BaseAdapter;
13import com.example.practice0924.base.BaseViewHolder;
14import com.example.practice0924.base.Const;
15import com.example.practice0924.base.EmptyActivity;
16import com.example.practice0924.databinding.ItemServeBinding;
17import com.example.practice0924.page.NewsDetailActivity;
18import com.example.practice0924.pojo.Serve;
19
20import java.util.List;
21
22public class ServeAdapter extends BaseAdapter<Serve.RowsBean, ItemServeBinding> {
23
24 public ServeAdapter(Context context, List<Serve.RowsBean> list){
25 super(context, list);
26 }
27
28
29
30 public BaseViewHolder<Serve.RowsBean, ItemServeBinding> onCreateViewHolder( ViewGroup parent, int viewType) {
31 return new ServeViewHolder(ItemServeBinding.inflate(LayoutInflater.from(context), parent, false));
32 }
33 class ServeViewHolder extends BaseViewHolder<Serve.RowsBean, ItemServeBinding>{
34
35 public ServeViewHolder(ItemServeBinding binding){
36 super(binding);
37 }
38
39
40 protected void refresh(int pos, Serve.RowsBean bean) {
41 binding.title.setText(bean.getServiceName());
42 String title = bean.getServiceName();
43 if(title.equals("更多服务")){
44 binding.icon.setImageResource(R.drawable.more_serve);
45 }else{
46 Glide.with(context).load(Const.URL+bean.getImgUrl()).into(binding.icon);
47 }
48
49 binding.getRoot().setOnClickListener(v->{
50 if(title.equals("更多服务")){
51 ((MainActivity)context).moreServe();
52 }else{
53 Intent intent = new Intent(context, EmptyActivity.class);
54 intent.putExtra("title", title);
55 context.startActivity(intent);
56 }
57 });
58 }
59 }
60}
61
在fragment内实例化适配器和布局管理器
xxxxxxxxxx
181 ServeAdapter serveAdapter = new ServeAdapter(requireActivity(), null);
2
3 binding.serveEnterRv.setLayoutManager(new GridLayoutManager(requireActivity(), 5));
4 binding.serveEnterRv.setAdapter(serveAdapter);
5 OKHttps.get("/prod-api/api/service/list?pageNum=1&pageSize=9", new Callbacks<Serve>() {
6
7
8 protected void getRes(String res, Serve bean) {
9 List<Serve.RowsBean> temps = new ArrayList<>(bean.getRows());
10
11 Serve.RowsBean moreServe = new Serve.RowsBean();
12 moreServe.setServiceName("更多服务");
13 temps.add(moreServe);
14
15 runOnUi(() -> serveAdapter.setData(temps));
16
17 }
18 });
先编写item布局然后生成pojo实现适配器
xxxxxxxxxx
311
2<androidx.cardview.widget.CardView
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:layout_margin="10dp"
8 app:cardCornerRadius="20dp"
9 >
10 <LinearLayout
11 android:layout_width="match_parent"
12 android:layout_height="wrap_content"
13 android:orientation="vertical">
14 <ImageView
15 android:layout_width="match_parent"
16 android:layout_height="100dp"
17 android:layout_gravity="center_horizontal"
18 android:scaleType="fitXY"
19 android:id="@+id/cover"/>
20
21 <TextView
22 android:layout_width="wrap_content"
23 android:layout_height="wrap_content"
24 android:layout_gravity="center_horizontal"
25 android:maxLines="1"
26 android:ellipsize="end"
27 android:text="title"
28 android:layout_marginStart="8dp"
29 android:id="@+id/title"/>
30 </LinearLayout>
31</androidx.cardview.widget.CardView>
Adapter
xxxxxxxxxx
471
2import android.content.Context;
3import android.content.Intent;
4import android.view.LayoutInflater;
5import android.view.ViewGroup;
6
7import androidx.annotation.NonNull;
8
9import com.bumptech.glide.Glide;
10import com.example.practice0924.base.BaseAdapter;
11import com.example.practice0924.base.BaseViewHolder;
12import com.example.practice0924.base.Const;
13import com.example.practice0924.databinding.ItemHotTopicBinding;
14import com.example.practice0924.page.NewsDetailActivity;
15import com.example.practice0924.pojo.News;
16import java.util.List;
17
18public class TopicAdapter extends BaseAdapter<News.RowsBean, ItemHotTopicBinding> {
19
20 public TopicAdapter(Context context, List<News.RowsBean> list){
21 super(context, list);
22 }
23
24
25
26 public BaseViewHolder<News.RowsBean, ItemHotTopicBinding> onCreateViewHolder( ViewGroup parent, int viewType) {
27 return new TopicViewHolder(ItemHotTopicBinding.inflate(LayoutInflater.from(context), parent, false));
28 }
29 class TopicViewHolder extends BaseViewHolder<News.RowsBean, ItemHotTopicBinding>{
30
31 public TopicViewHolder(ItemHotTopicBinding binding){
32 super(binding);
33 }
34
35
36 protected void refresh(int pos, News.RowsBean bean) {
37 binding.title.setText(bean.getTitle());
38 Glide.with(context).load(Const.URL+bean.getCover()).into(binding.cover);
39 binding.getRoot().setOnClickListener(v->{
40 Intent intent = new Intent(context, NewsDetailActivity.class);
41 intent.putExtra("id", bean.getId()+"");
42 context.startActivity(intent);
43 });
44 }
45 }
46}
47
fragment获取新闻热点列表,设置适配器
xxxxxxxxxx
111 TopicAdapter topicAdapter = new TopicAdapter(requireActivity(), null);
2 binding.hotTopicRv.setLayoutManager(new GridLayoutManager(requireActivity(), 2));
3 binding.hotTopicRv.setAdapter(topicAdapter);
4
5 OKHttps.get("/prod-api/press/press/list?hot=Y", new Callbacks<News>() {
6
7
8 protected void getRes(String res, News bean) {
9 runOnUi(() -> topicAdapter.setData(bean.getRows()));
10 }
11 });
生成分类pojo,获取后添加到TabLayout中,将id设置为tab的tag方便点击时获取tab的id
xxxxxxxxxx
161 NewsAdapter newsAdapter = new NewsAdapter(requireActivity(), null);
2 binding.newsRv.addItemDecoration(new DividerItemDecoration(requireActivity(), DividerItemDecoration.VERTICAL));
3 binding.newsRv.setLayoutManager(
4 new LinearLayoutManager(requireActivity(), RecyclerView.VERTICAL, false));
5 binding.newsRv.setAdapter(newsAdapter);
6 OKHttps.get("/prod-api/press/category/list", new Callbacks<NewsType>() {
7
8 protected void getRes(String res, NewsType bean) {
9 runOnUi(() -> {
10 for (NewsType.DataBean data : bean.getData()) {
11 binding.newsTypeTl.addTab(binding.newsTypeTl.newTab()
12 .setText(data.getName()).setTag(data.getId()));
13 }
14 });
15 }
16 });
复用旧的新闻pojo,编写新闻列表item布局
xxxxxxxxxx
481
2<RelativeLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 android:layout_width="match_parent"
5 android:layout_height="wrap_content"
6 android:layout_marginBottom="2dp">
7 <ImageView
8 android:id="@+id/cover"
9 android:layout_width="120dp"
10 android:layout_height="90dp"
11 android:layout_alignParentStart="true"
12 android:scaleType="fitXY"/>
13 <TextView
14 android:layout_width="match_parent"
15 android:layout_height="wrap_content"
16 android:id="@+id/title"
17 android:maxLines="1"
18 android:ellipsize="end"
19 android:textColor="#000"
20 android:text="title"
21 android:textSize="17sp"
22 android:layout_marginStart="128dp"
23 android:layout_marginTop="8dp"
24 />
25 <TextView
26 android:layout_width="match_parent"
27 android:layout_height="wrap_content"
28 android:id="@+id/desc"
29 android:maxLines="2"
30 android:ellipsize="end"
31 android:text="desc"
32 android:textColor="#222"
33 android:layout_marginTop="36dp"
34 android:layout_marginStart="128dp"
35 />
36 <TextView
37 android:layout_width="match_parent"
38 android:layout_height="wrap_content"
39 android:id="@+id/status"
40 android:maxLines="1"
41 android:ellipsize="end"
42 android:layout_marginStart="128dp"
43 android:layout_marginEnd="8dp"
44 android:text="2022-09-24"
45 android:gravity="end"
46 android:layout_alignBottom="@id/cover"
47 />
48</RelativeLayout>
编写适配器
xxxxxxxxxx
531
2import android.content.Context;
3import android.content.Intent;
4import android.text.Html;
5import android.view.LayoutInflater;
6import android.view.ViewGroup;
7
8import androidx.annotation.NonNull;
9
10import com.bumptech.glide.Glide;
11import com.example.practice0924.base.BaseAdapter;
12import com.example.practice0924.base.BaseViewHolder;
13import com.example.practice0924.base.Const;
14import com.example.practice0924.databinding.ItemNewsBinding;
15import com.example.practice0924.page.NewsDetailActivity;
16import com.example.practice0924.pojo.News;
17
18import java.util.List;
19
20public class NewsAdapter extends BaseAdapter<News.RowsBean, ItemNewsBinding> {
21
22 public NewsAdapter(Context context, List<News.RowsBean> list){
23 super(context, list);
24 }
25
26
27
28 public BaseViewHolder<News.RowsBean, ItemNewsBinding> onCreateViewHolder( ViewGroup parent, int viewType) {
29 return new NewsViewHolder(ItemNewsBinding.inflate(LayoutInflater.from(context), parent, false));
30 }
31 class NewsViewHolder extends BaseViewHolder<News.RowsBean, ItemNewsBinding>{
32
33 public NewsViewHolder(ItemNewsBinding binding){
34 super(binding);
35 }
36
37
38 protected void refresh(int pos, News.RowsBean bean) {
39 binding.title.setText(bean.getTitle());
40 Glide.with(context).load(Const.URL+bean.getCover()).into(binding.cover);
41 binding.desc.setText(Html.fromHtml(bean.getContent(),0));
42 Object obj = bean.getCommentNum();
43
44 binding.status.setText(String.format("评论总数:%d %s",obj==null?0:Integer.parseInt(obj.toString()), bean.getCreateTime()));
45 binding.getRoot().setOnClickListener(v->{
46 Intent intent = new Intent(context, NewsDetailActivity.class);
47 intent.putExtra("id", bean.getId()+"");
48 context.startActivity(intent);
49 });
50 }
51 }
52}
53
fragment内设置适配器,主页代码编写完成
xxxxxxxxxx
291
2NewsAdapter newsAdapter = new NewsAdapter(requireActivity(), null);
3binding.newsRv.addItemDecoration(new DividerItemDecoration(requireActivity(), DividerItemDecoration.VERTICAL));
4binding.newsRv.setLayoutManager(
5 new LinearLayoutManager(requireActivity(), RecyclerView.VERTICAL, false));
6binding.newsRv.setAdapter(newsAdapter);
7
8binding.newsTypeTl.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
9
10 public void onTabSelected(TabLayout.Tab tab) {
11 OKHttps.get("/prod-api/press/press/list?type="+tab.getTag(), new Callbacks<News>(){
12
13
14 protected void getRes(String res, News bean) {
15 runOnUi(()->newsAdapter.setData(bean.getRows()));
16 }
17 });
18 }
19
20
21 public void onTabUnselected(TabLayout.Tab tab) {
22
23 }
24
25
26 public void onTabReselected(TabLayout.Tab tab) {
27
28 }
29 });
xxxxxxxxxx
1561
2import android.content.Intent;
3
4import androidx.appcompat.widget.SearchView;
5import androidx.recyclerview.widget.DividerItemDecoration;
6import androidx.recyclerview.widget.GridLayoutManager;
7import androidx.recyclerview.widget.LinearLayoutManager;
8import androidx.recyclerview.widget.RecyclerView;
9
10import com.bumptech.glide.Glide;
11import com.example.practice0924.adapter.NewsAdapter;
12import com.example.practice0924.adapter.ServeAdapter;
13import com.example.practice0924.adapter.TopicAdapter;
14import com.example.practice0924.base.BaseFragment;
15import com.example.practice0924.base.Callbacks;
16import com.example.practice0924.base.Const;
17import com.example.practice0924.base.OKHttps;
18import com.example.practice0924.databinding.FragmentHomeBinding;
19import com.example.practice0924.pojo.HomeBanner;
20import com.example.practice0924.pojo.News;
21import com.example.practice0924.pojo.NewsType;
22import com.example.practice0924.pojo.Serve;
23import com.google.android.material.tabs.TabLayout;
24import com.youth.banner.adapter.BannerImageAdapter;
25import com.youth.banner.holder.BannerImageHolder;
26
27import java.util.ArrayList;
28import java.util.List;
29
30import okhttp3.OkHttp;
31
32public class HomeFragment extends BaseFragment<FragmentHomeBinding> {
33
34 protected void init() {
35
36 binding.searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
37
38 public boolean onQueryTextSubmit(String query) {
39 Const.SEARCH = query;
40 jump(NewsSearchActivity.class);
41// 防止直接enter搜索时触发两次,会关闭icon
42 binding.searchView.setIconified(true);
43 return true;
44 }
45
46
47 public boolean onQueryTextChange(String newText) {
48 return false;
49 }
50 });
51
52 OKHttps.get("/prod-api/api/rotation/list?type=2", new Callbacks<HomeBanner>() {
53
54
55 protected void getRes(String res, HomeBanner bean) {
56 if(isAdded()){
57 // 避免快速切换fragment报错
58 runOnUi(() -> {
59 List<HomeBanner.RowsBean> beans = bean.getRows();
60 binding.banner.setAdapter(new BannerImageAdapter<HomeBanner.RowsBean>(beans) {
61
62 public void onBindView(BannerImageHolder bannerImageHolder,
63 HomeBanner.RowsBean rowsBean, int i, int i1) {
64
65 Glide.with(requireActivity()).load(Const.URL + rowsBean.getAdvImg())
66 .into(bannerImageHolder.imageView);
67
68 bannerImageHolder.imageView.setOnClickListener(v -> {
69
70 Intent intent = new Intent(requireActivity(), NewsDetailActivity.class);
71 intent.putExtra("id", rowsBean.getTargetId() + "");
72 startActivity(intent);
73
74 });
75
76 }
77 });
78 });
79 }
80 }
81 });
82
83 ServeAdapter serveAdapter = new ServeAdapter(requireActivity(), null);
84
85 binding.serveEnterRv.setLayoutManager(new GridLayoutManager(requireActivity(), 5));
86 binding.serveEnterRv.setAdapter(serveAdapter);
87 OKHttps.get("/prod-api/api/service/list?pageNum=1&pageSize=9", new Callbacks<Serve>() {
88
89
90 protected void getRes(String res, Serve bean) {
91 List<Serve.RowsBean> temps = new ArrayList<>(bean.getRows());
92
93 Serve.RowsBean moreServe = new Serve.RowsBean();
94 moreServe.setServiceName("更多服务");
95 temps.add(moreServe);
96
97 runOnUi(() -> serveAdapter.setData(temps));
98
99 }
100 });
101
102 TopicAdapter topicAdapter = new TopicAdapter(requireActivity(), null);
103 binding.hotTopicRv.setLayoutManager(new GridLayoutManager(requireActivity(), 2));
104 binding.hotTopicRv.setAdapter(topicAdapter);
105
106 OKHttps.get("/prod-api/press/press/list?hot=Y", new Callbacks<News>() {
107
108
109 protected void getRes(String res, News bean) {
110 runOnUi(() -> topicAdapter.setData(bean.getRows()));
111 }
112 });
113
114 NewsAdapter newsAdapter = new NewsAdapter(requireActivity(), null);
115 binding.newsRv.addItemDecoration(new DividerItemDecoration(requireActivity(), DividerItemDecoration.VERTICAL));
116 binding.newsRv.setLayoutManager(
117 new LinearLayoutManager(requireActivity(), RecyclerView.VERTICAL, false));
118 binding.newsRv.setAdapter(newsAdapter);
119 OKHttps.get("/prod-api/press/category/list", new Callbacks<NewsType>() {
120
121 protected void getRes(String res, NewsType bean) {
122 runOnUi(() -> {
123 for (NewsType.DataBean data : bean.getData()) {
124 binding.newsTypeTl.addTab(binding.newsTypeTl.newTab()
125 .setText(data.getName()).setTag(data.getId()));
126 }
127 });
128 }
129 });
130
131 binding.newsTypeTl.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
132
133 public void onTabSelected(TabLayout.Tab tab) {
134 OKHttps.get("/prod-api/press/press/list?type="+tab.getTag(), new Callbacks<News>(){
135
136
137 protected void getRes(String res, News bean) {
138 runOnUi(()->newsAdapter.setData(bean.getRows()));
139 }
140 });
141 }
142
143
144 public void onTabUnselected(TabLayout.Tab tab) {
145
146 }
147
148
149 public void onTabReselected(TabLayout.Tab tab) {
150
151 }
152 });
153
154 }
155}
156
拥有新闻列表item和pojo类之后
在搜索Activity里获取传入搜索标题,获取新闻列表后进行比对返回结果列表复用列表适配器显示搜索结果
先在布局里加一个RecyclerView
xxxxxxxxxx
191
2<LinearLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 android:orientation="vertical"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:background="#eeeeee"
8 >
9
10 <include
11 android:id="@+id/toolbar"
12 layout="@layout/toolbar" />
13
14 <androidx.recyclerview.widget.RecyclerView
15 android:layout_width="match_parent"
16 android:layout_height="wrap_content"
17 android:id="@+id/newsRv"/>
18
19</LinearLayout>
直接先获取一个所有新闻列表,然后返回包含搜索文字的列表更新适配器
xxxxxxxxxx
531
2import android.view.View;
3
4import androidx.recyclerview.widget.DividerItemDecoration;
5import androidx.recyclerview.widget.LinearLayoutManager;
6import androidx.recyclerview.widget.RecyclerView;
7
8import com.example.practice0924.adapter.NewsAdapter;
9import com.example.practice0924.base.BaseActivity;
10import com.example.practice0924.base.Callbacks;
11import com.example.practice0924.base.Const;
12import com.example.practice0924.base.OKHttps;
13import com.example.practice0924.databinding.ActivityNewsSearchBinding;
14import com.example.practice0924.pojo.News;
15
16import java.util.ArrayList;
17import java.util.List;
18
19public class NewsSearchActivity extends BaseActivity<ActivityNewsSearchBinding> {
20 private List<News.RowsBean> news = new ArrayList<>();
21
22 protected void init() {
23 binding.toolbar.title.setText(String.format("‘%s’搜索结果", Const.SEARCH));
24
25 binding.toolbar.back.setVisibility(View.VISIBLE);
26 binding.toolbar.back.setOnClickListener(v->finish());
27
28 NewsAdapter newsAdapter = new NewsAdapter(this, null);
29 binding.newsRv.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
30 binding.newsRv.setLayoutManager(
31 new LinearLayoutManager(this, RecyclerView.VERTICAL, false));
32 binding.newsRv.setAdapter(newsAdapter);
33
34 OKHttps.get("/prod-api/press/press/list",new Callbacks<News>(){
35
36
37 protected void getRes(String res, News bean) {
38 runOnUiThread(()-> {
39 news = new ArrayList<>(bean.getRows());
40 List<News.RowsBean> search = new ArrayList<>();
41 for(News.RowsBean row:news){
42 if(row.getTitle().contains(Const.SEARCH)){
43 search.add(row);
44 }
45 }
46 newsAdapter.setData(search);
47 if(search.size()==0) toast("搜索结果为空");
48 });
49 }
50 });
51
52 }
53}
这里新闻详情可以顺手补完新闻fragment,fragment的实现会相对简单
就是主页面底部一个tablayout+rv的形式
xxxxxxxxxx
181
2<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 android:orientation="vertical"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:background="#eeeeee">
8 <com.google.android.material.tabs.TabLayout
9 android:layout_width="match_parent"
10 android:layout_height="wrap_content"
11 app:tabMode="auto"
12 android:id="@+id/newsTypeTl"/>
13
14 <androidx.recyclerview.widget.RecyclerView
15 android:layout_width="match_parent"
16 android:layout_height="wrap_content"
17 android:id="@+id/newsRv"/>
18</LinearLayout>
新闻详情比较麻烦,用上了webview显示内容,然后加上推荐新闻的rv,底部发表评论,以及底部查看评论列表
webView加载一个图片比较多的新闻时报了oom错误,换一个加载方式,但是另一个方式直接全部加载不了图片
xxxxxxxxxx
851
2<RelativeLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 android:orientation="vertical"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:background="#eeeeee"
8 >
9
10 <include
11 android:id="@+id/toolbar"
12 layout="@layout/toolbar" />
13 <androidx.core.widget.NestedScrollView
14 android:layout_width="match_parent"
15 android:layout_height="match_parent"
16 android:layout_marginTop="?actionBarSize"
17 android:layout_marginBottom="56dp"
18 >
19
20 <LinearLayout
21 android:layout_width="match_parent"
22 android:layout_height="match_parent"
23 android:orientation="vertical"
24 >
25 <ImageView
26 android:layout_width="match_parent"
27 android:layout_height="wrap_content"
28 android:scaleType="fitXY"
29 android:id="@+id/cover"
30 />
31 <TextView
32 android:layout_width="match_parent"
33 android:layout_height="wrap_content"
34 android:id="@+id/content"
35 />
36 <TextView
37 android:layout_width="match_parent"
38 android:layout_height="wrap_content"
39 android:text="推荐新闻"
40 android:textSize="17sp"
41 android:layout_marginLeft="8dp"
42 />
43 <androidx.recyclerview.widget.RecyclerView
44 android:layout_width="match_parent"
45 android:layout_height="wrap_content"
46 android:layout_marginHorizontal="8dp"
47 android:id="@+id/recommendRv"/>
48
49 <TextView
50 android:layout_width="match_parent"
51 android:layout_height="wrap_content"
52 android:text="评论"
53 android:textSize="17sp"
54 android:layout_marginLeft="8dp"
55 />
56 <androidx.recyclerview.widget.RecyclerView
57 android:layout_width="match_parent"
58 android:layout_height="wrap_content"
59 android:layout_marginHorizontal="8dp"
60 android:id="@+id/commentRv"/>
61 </LinearLayout>
62
63 </androidx.core.widget.NestedScrollView>
64
65 <LinearLayout
66 android:layout_width="match_parent"
67 android:layout_height="56dp"
68 android:orientation="horizontal"
69 android:background="#fff"
70 android:layout_alignParentBottom="true">
71 <EditText
72 android:layout_width="0dp"
73 android:layout_height="wrap_content"
74 android:id="@+id/comment_et"
75 android:hint="如何评价..."
76 android:focusable="false"
77 android:layout_weight="7"/>
78 <Button
79 android:layout_width="0dp"
80 android:layout_height="wrap_content"
81 android:id="@+id/comment_bt"
82 android:text="评论"
83 android:layout_weight="2"/>
84 </LinearLayout>
85</RelativeLayout>
因为显示的状态文字不同,要求观看数和点赞数。这里基于复用的原则,直接给NewsAdapter加上一个type变量用于指明显示的状态文字
xxxxxxxxxx
41String status = String.format("评论总数:%d %s", obj == null ? 0 : Integer.parseInt(obj.toString()), bean.getCreateTime());
2 if (type != 0) {
3 status = String.format("👁:%d 👍:%d %s", bean.getReadNum(), bean.getLikeNum(), bean.getCreateTime());
4 }
先测试接口生成新闻详情pojo,然后在NewsDetail.Activity内获取传入的id,get请求获取新闻详情bean
加载内容到webView时还要注意设置url给内容中的图片链接
xxxxxxxxxx
131String id = getIntent().getStringExtra("id");
2
3 get("/prod-api/press/press/" + id, new Callbacks<NewsDetail>() {
4
5
6 protected void getRes(String res, NewsDetail bean) {
7 NewsDetail.DataBean data = bean.getData();
8 runOnUiThread(() -> {
9 binding.toolbar.title.setText(data.getTitle());
10 binding.content.setText(Html.fromHtml(data.getContent(), 0));
11 });
12 }
13 });
复用改改参数就行
xxxxxxxxxx
111 NewsAdapter rcAdapter = new NewsAdapter(NewsDetailActivity.this, null, 1);
2 binding.recommendRv.addItemDecoration(new DividerItemDecoration(NewsDetailActivity.this, DividerItemDecoration.VERTICAL));
3 binding.recommendRv.setLayoutManager(
4 new LinearLayoutManager(NewsDetailActivity.this, RecyclerView.VERTICAL, false));
5 binding.recommendRv.setAdapter(rcAdapter);
6 OKHttps.get("/prod-api/press/press/list?top=Y", new Callbacks<News>() {
7
8 protected void getRes(String res, News bean) {
9 runOnUiThread(() -> rcAdapter.setData(bean.getRows()));
10 }
11 });
服务器接口没写好,搁置了
因为评论接口看不了,这里干脆不将评论内容post上去了,直接toast敷衍一下
xxxxxxxxxx
121binding.commentBt.setOnClickListener(v->{
2 if(!TextUtils.isEmpty(Const.TOKEN_VAL.trim())){
3 String inp = binding.commentEt.getText().toString().trim();
4 if(!TextUtils.isEmpty(inp)){
5 toast("评论成功");
6 }else{
7 toast("输入为空");
8 }
9 }else{
10 toast("未登录");
11 }
12});
xxxxxxxxxx
721
2import android.text.Html;
3import android.text.TextUtils;
4import android.view.View;
5
6import androidx.recyclerview.widget.DividerItemDecoration;
7import androidx.recyclerview.widget.LinearLayoutManager;
8import androidx.recyclerview.widget.RecyclerView;
9
10import com.example.practice0924.adapter.NewsAdapter;
11import com.example.practice0924.base.BaseActivity;
12
13
14import com.example.practice0924.base.Callbacks;
15import com.example.practice0924.base.Const;
16import com.example.practice0924.base.OKHttps;
17import com.example.practice0924.databinding.ActivityNewsDeatilBinding;
18import com.example.practice0924.pojo.News;
19import com.example.practice0924.pojo.NewsDetail;
20
21
22public class NewsDetailActivity extends BaseActivity<ActivityNewsDeatilBinding> {
23
24 protected void init() {
25 binding.toolbar.title.setText("新闻详情");
26
27 binding.toolbar.back.setVisibility(View.VISIBLE);
28 binding.toolbar.back.setOnClickListener(v -> finish());
29
30 String id = getIntent().getStringExtra("id");
31
32 OKHttps.get("/prod-api/press/press/" + id, new Callbacks<NewsDetail>() {
33
34
35 protected void getRes(String res, NewsDetail bean) {
36 NewsDetail.DataBean data = bean.getData();
37 runOnUiThread(() -> {
38 binding.toolbar.title.setText(data.getTitle());
39 binding.content.setText(Html.fromHtml(data.getContent().trim(), 0));
40 });
41 }
42 });
43
44 NewsAdapter rcAdapter = new NewsAdapter(NewsDetailActivity.this, null, 1);
45 binding.recommendRv.addItemDecoration(new DividerItemDecoration(NewsDetailActivity.this, DividerItemDecoration.VERTICAL));
46 binding.recommendRv.setLayoutManager(
47 new LinearLayoutManager(NewsDetailActivity.this, RecyclerView.VERTICAL, false));
48 binding.recommendRv.setAdapter(rcAdapter);
49 OKHttps.get("/prod-api/press/press/list?top=Y", new Callbacks<News>() {
50
51 protected void getRes(String res, News bean) {
52 runOnUiThread(() -> rcAdapter.setData(bean.getRows()));
53 }
54 });
55
56 binding.commentBt.setOnClickListener(v->{
57 if(!TextUtils.isEmpty(Const.TOKEN_VAL.trim())){
58 String inp = binding.commentEt.getText().toString().trim();
59 if(!TextUtils.isEmpty(inp)){
60 toast("评论成功");
61 }else{
62 toast("输入为空");
63 }
64 }else{
65 toast("未登录");
66 }
67 });
68
69
70
71 }
72}
这个主要麻烦在要做一个单选RV
这个搜索框比用SearchView方便,后续再写主页搜索应该会用这种形式来减少记忆负担
xxxxxxxxxx
261
2<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 android:orientation="vertical"
4 android:layout_width="match_parent"
5 android:layout_height="match_parent"
6 android:background="#eeeeee">
7 <androidx.recyclerview.widget.RecyclerView
8 android:layout_width="100dp"
9 android:layout_height="match_parent"
10 android:background="#fff"
11 android:id="@+id/selectorRv"/>
12 <EditText
13 android:id="@+id/searchEt"
14 android:drawableLeft="@drawable/ic_search"
15 android:hint="服务搜索"
16 android:layout_marginStart="100dp"
17 android:layout_width="match_parent"
18 android:layout_height="50dp"/>
19
20 <androidx.recyclerview.widget.RecyclerView
21 android:layout_width="match_parent"
22 android:layout_height="match_parent"
23 android:layout_marginStart="100dp"
24 android:layout_marginTop="50dp"
25 android:id="@+id/serveRv"/>
26</RelativeLayout>
测试服务分类接口生成pojo,在rowsBean 添加一个布尔值标识被点击
emm...新版好像没有服务分类接口,那简单设置成存到HashMap嵌套List根据serveType存储即可
首先在Serve pojo类添加isChecked 和区分服务的serviceCategory
xxxxxxxxxx
81private boolean isChecked;
2public boolean isChecked() {
3 return isChecked;
4}
5
6public void setChecked(boolean checked) {
7 isChecked = checked;
8}
这里item binding直接新建一个包含一个Tv的布局
xxxxxxxxxx
161<?xml version="1.0" encoding="utf-8"?>
2<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="match_parent"
4 android:layout_height="wrap_content"
5 android:background="#fff"
6 >
7 <TextView
8 android:layout_width="match_parent"
9 android:layout_height="match_parent"
10 android:text="全部服务"
11 android:id="@+id/title"
12 android:textSize="18sp"
13 android:layout_margin="4dp"
14 android:gravity="center"
15 />
16</LinearLayout>
然后新建适配器,
关键是暴露一个表示上一个点击item坐标的last 整型,一个为了省事用于回调点击事件的onclick runnable
xxxxxxxxxx
111 int last = 0;
2
3public int getLast() {
4 return last;
5}
6
7Runnable onClick = null;
8
9public void setOnClick(Runnable onClick) {
10 this.onClick = onClick;
11}
然后通过Serve中新建的checked变量判断是否更改背景色,在点击时进行last坐标的更新,更改上一次点击的item和当前item
xxxxxxxxxx
161 binding.getRoot().setOnClickListener(v->{
2 list.get(last).setChecked(false);
3 list.get(pos).setChecked(true);
4 notifyItemChanged(last);
5 notifyItemChanged(pos);
6 last = pos;
7 if(onClick!=null){
8 onClick.run();
9 }
10 });
11
12if(bean.isChecked()){
13 binding.getRoot().setBackgroundColor(0xFFCCCCCC);
14}else{
15 binding.getRoot().setBackgroundColor(0xFFFFFFFF);
16}
完整适配器代码
xxxxxxxxxx
681
2import android.content.Context;
3import android.view.LayoutInflater;
4import android.view.ViewGroup;
5
6import androidx.annotation.NonNull;
7
8import com.example.practice0924.base.BaseAdapter;
9import com.example.practice0924.base.BaseViewHolder;
10import com.example.practice0924.databinding.ItemServeTypeBinding;
11import com.example.practice0924.pojo.Serve;
12
13import java.util.List;
14
15public class ServeTypeAdapter extends BaseAdapter<Serve.RowsBean, ItemServeTypeBinding> {
16 int last = 0;
17
18 public int getLast() {
19 return last;
20 }
21
22 Runnable onClick = null;
23
24 public void setOnClick(Runnable onClick) {
25 this.onClick = onClick;
26 }
27
28 public ServeTypeAdapter(Context context, List<Serve.RowsBean> list){
29 super(context, list);
30 }
31
32
33
34 public BaseViewHolder<Serve.RowsBean, ItemServeTypeBinding> onCreateViewHolder( ViewGroup parent, int viewType) {
35 return new ServeViewHolder(ItemServeTypeBinding.inflate(LayoutInflater.from(context), parent, false));
36 }
37 class ServeViewHolder extends BaseViewHolder<Serve.RowsBean, ItemServeTypeBinding>{
38
39 public ServeViewHolder(ItemServeTypeBinding binding){
40 super(binding);
41 }
42
43
44 protected void refresh(int pos, Serve.RowsBean bean) {
45
46 binding.title.setText(bean.getServiceType());
47
48 binding.getRoot().setOnClickListener(v->{
49 list.get(last).setChecked(false);
50 list.get(pos).setChecked(true);
51 notifyItemChanged(last);
52 notifyItemChanged(pos);
53 last = pos;
54 if(onClick!=null){
55 onClick.run();
56 }
57 });
58
59 if(bean.isChecked()){
60 binding.getRoot().setBackgroundColor(0xFFCCCCCC);
61 }else{
62 binding.getRoot().setBackgroundColor(0xFFFFFFFF);
63 }
64
65 }
66 }
67}
68
xxxxxxxxxx
551HashMap<String, List<Serve.RowsBean>> serves = new HashMap<>();
2List<Serve.RowsBean> types = new ArrayList<>();
3
4 serves.put("全部服务", new ArrayList<>());
5
6Serve.RowsBean row = new Serve.RowsBean();
7row.setServiceType("全部服务");
8row.setChecked(true);
9types.add(row);
10
11ServeTypeAdapter serveTypeAdapter = new ServeTypeAdapter(requireActivity(), types);
12binding.selectorRv.addItemDecoration(new DividerItemDecoration(requireActivity(), DividerItemDecoration.VERTICAL));
13binding.selectorRv.setLayoutManager(new LinearLayoutManager(requireActivity(), RecyclerView.VERTICAL,false));
14binding.selectorRv.setAdapter(serveTypeAdapter);
15
16
17ServeAdapter serveAdapter = new ServeAdapter(requireActivity(), null);
18binding.serveRv.setLayoutManager(new GridLayoutManager(requireActivity(), 3));
19binding.serveRv.setAdapter(serveAdapter);
20
21serveTypeAdapter.setOnClick(()->{
22 serveAdapter.setData(serves.get(types.get(serveTypeAdapter.getLast()).getServiceType()));
23});
24
25OKHttps.get("/prod-api/api/service/list", new Callbacks<Serve>() {
26
27
28 protected void getRes(String res, Serve bean) {
29 if (isAdded()) {
30 List<Serve.RowsBean> temps = new ArrayList<>(bean.getRows());
31
32 for (int i=0;i<temps.size();i++) {
33 Serve.RowsBean row = temps.get(i);
34 if (serves.get(row.getServiceType()) == null) {
35 serves.put(row.getServiceType(), new ArrayList<>());
36 }
37 serves.get(row.getServiceType()).add(row);
38 serves.get("全部服务").add(row);
39 }
40
41 for(String key:serves.keySet()){
42 Serve.RowsBean row = serves.get(key).get(0);
43 if("全部服务".equals(key)){
44 continue;
45 }
46 types.add(row);
47 }
48
49
50 runOnUi(() -> {
51 serveAdapter.setData(temps);
52 serveTypeAdapter.setData(types);
53 });
54 }
55
HashMap里装获取全部服务时手动分类的服务List
另用一个List存放每种服务的bean,这里还得新建一个全部服务的List和相应的bean 操作完之后就可以在网络请求内同步更新两个适配器
edit监听enter
这里有个坑点,清理完文本的时候会自动加上一个空格导致再次搜索匹配不上,只要加个trim把输入的前后空格清理一下就可以了
xxxxxxxxxx
151binding.searchEt.setOnEditorActionListener((v, actionId, event) -> {
2 if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
3 List<Serve.RowsBean> temps = new ArrayList<>();
4
5 for (Serve.RowsBean bean : serves.get("全部服务")) {
6 if (bean.getServiceName().contains(binding.searchEt.getText().toString().trim())) {
7 temps.add(bean);
8 }
9 }
10 binding.searchEt.setText("");
11 serveAdapter.setData(temps);
12 toast(String.format("查询到%d条结果", temps.size()));
13 }
14 return false;
15 });
xxxxxxxxxx
1021
2import android.view.KeyEvent;
3import androidx.recyclerview.widget.DividerItemDecoration;
4import androidx.recyclerview.widget.GridLayoutManager;
5import androidx.recyclerview.widget.LinearLayoutManager;
6import androidx.recyclerview.widget.RecyclerView;
7
8import com.example.practice0924.adapter.ServeAdapter;
9import com.example.practice0924.adapter.ServeTypeAdapter;
10import com.example.practice0924.base.BaseFragment;
11import com.example.practice0924.base.Callbacks;
12import com.example.practice0924.base.OKHttps;
13import com.example.practice0924.databinding.FragmentServeBinding;
14import com.example.practice0924.pojo.Serve;
15
16import java.util.ArrayList;
17import java.util.HashMap;
18import java.util.List;
19
20public class ServeFragment extends BaseFragment<FragmentServeBinding> {
21 HashMap<String, List<Serve.RowsBean>> serves = new HashMap<>();
22 List<Serve.RowsBean> types = new ArrayList<>();
23
24
25 protected void init() {
26
27
28 serves.put("全部服务", new ArrayList<>());
29
30 Serve.RowsBean row = new Serve.RowsBean();
31 row.setServiceType("全部服务");
32 row.setChecked(true);
33 types.add(row);
34
35 ServeTypeAdapter serveTypeAdapter = new ServeTypeAdapter(requireActivity(), types);
36 binding.selectorRv.addItemDecoration(new DividerItemDecoration(requireActivity(), DividerItemDecoration.VERTICAL));
37 binding.selectorRv.setLayoutManager(new LinearLayoutManager(requireActivity(), RecyclerView.VERTICAL, false));
38 binding.selectorRv.setAdapter(serveTypeAdapter);
39
40
41 ServeAdapter serveAdapter = new ServeAdapter(requireActivity(), null);
42 binding.serveRv.setLayoutManager(new GridLayoutManager(requireActivity(), 3));
43 binding.serveRv.setAdapter(serveAdapter);
44
45 serveTypeAdapter.setOnClick(() -> {
46 serveAdapter.setData(serves.get(types.get(serveTypeAdapter.getLast()).getServiceType()));
47 });
48
49 binding.searchEt.setOnEditorActionListener((v, actionId, event) -> {
50 if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
51 List<Serve.RowsBean> temps = new ArrayList<>();
52
53 for (Serve.RowsBean bean : serves.get("全部服务")) {
54 if (bean.getServiceName().contains(binding.searchEt.getText().toString().trim())) {
55 temps.add(bean);
56 }
57 }
58 binding.searchEt.setText("");
59 serveAdapter.setData(temps);
60 toast(String.format("查询到%d条结果", temps.size()));
61 }
62 return false;
63 });
64
65 OKHttps.get("/prod-api/api/service/list", new Callbacks<Serve>() {
66
67
68 protected void getRes(String res, Serve bean) {
69 if (isAdded()) {
70 List<Serve.RowsBean> temps = new ArrayList<>(bean.getRows());
71
72 for (int i = 0; i < temps.size(); i++) {
73 Serve.RowsBean row = temps.get(i);
74 if (serves.get(row.getServiceType()) == null) {
75 serves.put(row.getServiceType(), new ArrayList<>());
76 }
77 serves.get(row.getServiceType()).add(row);
78 serves.get("全部服务").add(row);
79 }
80
81 for (String key : serves.keySet()) {
82 Serve.RowsBean row = serves.get(key).get(0);
83 if ("全部服务".equals(key)) {
84 continue;
85 }
86 types.add(row);
87 }
88
89
90 runOnUi(() -> {
91 serveAdapter.setData(temps);
92 serveTypeAdapter.setData(types);
93 });
94 }
95
96 }
97 });
98
99
100 }
101}
102
xxxxxxxxxx
1471<?xml version="1.0" encoding="utf-8"?>
2<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 android:orientation="vertical"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:background="#eeeeee"
8
9 >
10 <androidx.cardview.widget.CardView
11 android:layout_width="match_parent"
12 android:layout_height="wrap_content"
13 app:cardCornerRadius="16dp"
14 android:background="#fff"
15 android:layout_margin="8dp"
16 >
17 <RelativeLayout
18 android:layout_width="match_parent"
19 android:layout_height="wrap_content">
20 <ImageView
21 android:layout_width="64dp"
22 android:layout_height="64dp"
23 android:id="@+id/head"
24 android:scaleType="fitXY"
25 android:src="@mipmap/icon"
26 android:layout_margin="8dp"
27 />
28 <TextView
29 android:layout_width="wrap_content"
30 android:layout_height="wrap_content"
31 android:maxLines="1"
32 android:textSize="22sp"
33 android:textColor="#000"
34 android:id="@+id/nickName"
35 android:text="未登录"
36 android:layout_marginStart="80dp"
37 android:layout_centerVertical="true"
38 />
39 <ImageView
40 android:layout_width="128dp"
41 android:layout_height="64dp"
42 android:layout_alignParentEnd="true"
43 android:scaleType="fitXY"
44 android:id="@+id/balance_bg"
45 android:src="@drawable/balance_bg"
46 android:layout_margin="8dp"
47 />
48 <TextView
49 android:layout_width="128dp"
50 android:layout_height="wrap_content"
51 android:maxLines="1"
52 android:text="未登录"
53 android:gravity="center"
54 android:ellipsize="end"
55 android:textSize="20sp"
56 android:textColor="#fff"
57 android:id="@+id/balance"
58 android:layout_alignEnd="@+id/balance_bg"
59 android:layout_centerVertical="true"
60 />
61 </RelativeLayout>
62 </androidx.cardview.widget.CardView>
63 <com.google.android.material.card.MaterialCardView
64 android:layout_width="match_parent"
65 android:layout_margin="8dp"
66 android:clickable="true"
67 app:cardCornerRadius="8dp"
68 app:strokeColor="#000"
69 app:strokeWidth="2dp"
70 android:layout_height="50dp">
71 <TextView
72 android:layout_width="match_parent"
73 android:layout_height="wrap_content"
74 android:textSize="20sp"
75 android:layout_marginHorizontal="8dp"
76 android:layout_gravity="center"
77 android:drawableRight="@drawable/ic_enter"
78 android:id="@+id/perMsg"
79 android:text="个人信息"
80 />
81 </com.google.android.material.card.MaterialCardView>
82 <com.google.android.material.card.MaterialCardView
83 android:layout_width="match_parent"
84 android:layout_margin="8dp"
85 android:clickable="true"
86 app:cardCornerRadius="8dp"
87 app:strokeColor="#000"
88 app:strokeWidth="2dp"
89 android:layout_height="50dp">
90 <TextView
91 android:layout_width="match_parent"
92 android:layout_height="wrap_content"
93 android:textSize="20sp"
94 android:layout_marginHorizontal="8dp"
95 android:layout_gravity="center"
96 android:drawableRight="@drawable/ic_enter"
97 android:id="@+id/orderList"
98 android:text="订单列表"
99 />
100 </com.google.android.material.card.MaterialCardView>
101 <com.google.android.material.card.MaterialCardView
102 android:layout_width="match_parent"
103 android:layout_margin="8dp"
104 app:cardCornerRadius="8dp"
105 app:strokeColor="#000"
106 android:clickable="true"
107 app:strokeWidth="2dp"
108 android:layout_height="50dp">
109 <TextView
110 android:layout_width="match_parent"
111 android:layout_height="wrap_content"
112 android:textSize="20sp"
113 android:layout_marginHorizontal="8dp"
114 android:layout_gravity="center"
115 android:drawableRight="@drawable/ic_enter"
116 android:id="@+id/pwdUpdate"
117 android:text="修改密码"
118 />
119 </com.google.android.material.card.MaterialCardView>
120 <com.google.android.material.card.MaterialCardView
121 android:layout_width="match_parent"
122 android:layout_margin="8dp"
123 app:cardCornerRadius="8dp"
124 app:strokeColor="#000"
125 android:clickable="true"
126 app:strokeWidth="2dp"
127 android:layout_height="50dp">
128 <TextView
129 android:layout_width="match_parent"
130 android:layout_height="wrap_content"
131 android:textSize="20sp"
132 android:layout_marginHorizontal="8dp"
133 android:layout_gravity="center"
134 android:drawableRight="@drawable/ic_enter"
135 android:id="@+id/feedback"
136 android:text="意见反馈"
137 />
138 </com.google.android.material.card.MaterialCardView>
139 <Button
140 android:layout_width="match_parent"
141 android:layout_height="60dp"
142 android:layout_marginHorizontal="8dp"
143 android:text="登录"
144 android:textSize="20sp"
145 android:id="@+id/switchStatus"
146 />
147</LinearLayout>
主要是按钮边框通过MaterialCardView实现
首先页面需要判断是否登录,登录了则应该通过TOKEN获取到个人信息显示到首页
因为更新个人信息后会返回到fragment,所以需要在页面onResume方法中获取个人信息以实现实时更新请求
通过TOKEN_VAL判断是否登录,进入页面时先从SP中重新赋值TOKEN判断是否登录,同理此处在onResume中获取
xxxxxxxxxx
91
2 public void onResume() {
3 super.onResume();
4 TOKEN_VAL = getSP(TOKEN);
5 isLogin = !TextUtils.isEmpty(TOKEN_VAL.trim());
6 if(!isLogin){
7 toast("未登录");
8 }
9 }
这里准备编写一个登录注册的页面,记得新建Activity后一定要在清单中确认添加了相应节点。可以一开始就确认好预计要使用的Activity,一口气建好并添加。以及结合限选的主题时记得检查清单文件有没有合并避免无法打开限选主题的界面
登录注册的界面可以提前写好多个输入框,这样可以使登录,注册,修改密码使用一个界面完成。
这里因为注册需要用户名,密码,电话号码和输入性别。所以这个页面需要多个输入框和一个单选框
一个tv用于切换注册登录,一个按钮用于提交
xxxxxxxxxx
1501
2<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 android:orientation="vertical"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:background="#eeeeee">
8
9 <include
10 android:id="@+id/toolbar"
11 layout="@layout/toolbar" />
12 <LinearLayout
13 android:layout_width="match_parent"
14 android:layout_height="match_parent"
15 android:gravity="center"
16 android:orientation="vertical"
17 >
18 <com.google.android.material.card.MaterialCardView
19 android:layout_width="match_parent"
20 android:layout_height="60dp"
21 android:background="#fff"
22 android:layout_margin="8dp"
23 app:strokeWidth="1dp"
24 app:strokeColor="@color/colorPrimaryDark"
25 app:cardCornerRadius="8dp"
26 >
27 <EditText
28 android:layout_width="match_parent"
29 android:layout_height="match_parent"
30 android:layout_margin="4dp"
31 android:background="@null"
32 android:id="@+id/eta"
33 android:hint="用户名"/>
34 </com.google.android.material.card.MaterialCardView>
35 <com.google.android.material.card.MaterialCardView
36 android:layout_width="match_parent"
37 android:layout_height="60dp"
38 android:background="#fff"
39 android:layout_margin="8dp"
40 app:strokeWidth="1dp"
41 app:strokeColor="@color/colorPrimaryDark"
42 app:cardCornerRadius="8dp"
43 >
44 <EditText
45 android:layout_width="match_parent"
46 android:layout_height="match_parent"
47 android:layout_margin="4dp"
48 android:id="@+id/etb"
49 android:inputType="textPassword"
50 android:background="@null"
51 android:hint="密码"/>
52 </com.google.android.material.card.MaterialCardView>
53 <com.google.android.material.card.MaterialCardView
54 android:layout_width="match_parent"
55 android:layout_height="60dp"
56 android:background="#fff"
57 android:layout_margin="8dp"
58 app:strokeWidth="1dp"
59 android:inputType="textPassword"
60 android:visibility="gone"
61 app:strokeColor="@color/colorPrimaryDark"
62 app:cardCornerRadius="8dp"
63 >
64 <EditText
65 android:layout_width="match_parent"
66 android:layout_height="match_parent"
67 android:layout_margin="4dp"
68 android:background="@null"
69 android:id="@+id/etc"
70 android:inputType="textPassword"
71 android:hint="重复密码"/>
72 </com.google.android.material.card.MaterialCardView>
73 <com.google.android.material.card.MaterialCardView
74 android:layout_width="match_parent"
75 android:layout_height="60dp"
76 android:background="#fff"
77 android:layout_margin="8dp"
78 app:strokeWidth="1dp"
79 android:visibility="gone"
80 app:strokeColor="@color/colorPrimaryDark"
81 app:cardCornerRadius="8dp"
82 >
83 <EditText
84 android:layout_width="match_parent"
85 android:layout_height="match_parent"
86 android:layout_margin="4dp"
87 android:background="@null"
88 android:inputType="phone"
89 android:id="@+id/etd"
90 android:hint="电话号码"/>
91 </com.google.android.material.card.MaterialCardView><com.google.android.material.card.MaterialCardView
92 android:layout_width="match_parent"
93 android:layout_height="60dp"
94 android:background="#fff"
95 android:layout_margin="8dp"
96 app:strokeWidth="1dp"
97 android:visibility="gone"
98 app:strokeColor="@color/colorPrimaryDark"
99 app:cardCornerRadius="8dp"
100 >
101 <EditText
102 android:layout_width="match_parent"
103 android:layout_height="match_parent"
104 android:layout_margin="4dp"
105 android:background="@null"
106 android:id="@+id/ete"
107 android:hint="重复密码"/>
108 </com.google.android.material.card.MaterialCardView>
109 <RadioGroup
110 android:layout_width="match_parent"
111 android:layout_height="wrap_content"
112 android:gravity="center"
113 android:id="@+id/sexSelector"
114 android:visibility="gone"
115 android:orientation="horizontal"
116 >
117 <TextView
118 android:layout_width="wrap_content"
119 android:layout_height="wrap_content"
120 android:text="性别"/>
121 <RadioButton
122 android:layout_width="wrap_content"
123 android:layout_height="wrap_content"
124 android:id="@+id/male"
125 android:checked="true"
126 android:text="男"/>
127 <RadioButton
128 android:layout_width="wrap_content"
129 android:layout_height="wrap_content"
130 android:id="@+id/female"
131 android:text="女"/>
132 </RadioGroup>
133 <TextView
134 android:layout_width="wrap_content"
135 android:layout_height="wrap_content"
136 android:text="注册账号"
137 android:textColor="@color/colorPrimaryDark"
138 android:id="@+id/registerTip"/>
139
140 <Button
141 android:layout_width="match_parent"
142 android:layout_height="60dp"
143 android:layout_marginHorizontal="8dp"
144 android:text="登录"
145 android:textSize="20sp"
146 android:id="@+id/todo"
147 />
148 </LinearLayout>
149
150</LinearLayout>
编写LoginActivity代码 为了区别登录注册两种状态和重置密码的状态,通过重置密码跳转时会传一个布尔值标识重置密码。否则则是默认登录界面,于是PerFragment个人中心暂时像这样子 注意token有过期的可能,onResume获取个人信息的时候应注意token过期,过期视为未登录
xxxxxxxxxx
331
2public class PerFragment extends BaseFragment<FragmentPerBinding> {
3 boolean isLogin;
4
5 protected void init() {
6 binding.pwdUpdate.setOnClickListener(v->{
7 Intent intent = new Intent(requireActivity(), LoginActivity.class);
8 intent.putExtra("isResetPwd", true);
9 requireActivity().startActivity(intent);
10 });
11 }
12 void updateView(){
13 binding.switchStatus.setText(isLogin?"退出":"登录");
14 binding.switchStatus.setOnClickListener(v->{
15 if(!isLogin){
16 jump(LoginActivity.class);
17 }else{
18 TOKEN_VAL = "";
19 setSp(TOKEN,TOKEN_VAL);
20 updateView();
21 }
22 });
23 // 获取个人信息
24 }
25
26 public void onResume() {
27 super.onResume();
28 TOKEN_VAL = getSP(TOKEN);
29 isLogin = !TextUtils.isEmpty(TOKEN_VAL.trim());
30 updateView();
31 }
32}
33
接下来实现登录界面的三种状态,先根据传入和点击tip tv切换页面状态再完善代码实现,因为id简朴的用了字典序,所以最好在注释内简单说明一下哪些ed的类型,默认显示哪些
xxxxxxxxxx
61/*
2 * ea,ee common
3 * eb,ec pwd
4 * ed phone
5 * default ea,eb
6 * */
封装批量控制编辑框显示,内容判空的方法
xxxxxxxxxx
281 boolean isEmpty(EditText... et){
2 for(EditText e:et){
3 if(TextUtils.isEmpty(e.getText().toString().trim())){
4 return true;
5 }
6 }
7 return false;
8 }
9 void vis(View... views){
10 for(View v:views){
11 if(v instanceof EditText){
12 ((View)v.getParent()).setVisibility(View.VISIBLE);
13 }
14 else{
15 v.setVisibility(View.VISIBLE);
16 }
17 }
18 }
19 void gone(View... views){
20 for(View v:views){
21 if(v instanceof EditText){
22 ((View)v.getParent()).setVisibility(View.GONE);
23 }
24 else{
25 v.setVisibility(View.GONE);
26 }
27 }
28 }
测试登录,注册和修改密码接口。新建通用返回结果pojo,token是为了在返回token的登录接口的返回共同使用而加上的
没有token的返回直接无视这个属性即可
xxxxxxxxxx
1641
2import android.text.TextUtils;
3import android.view.View;
4import android.widget.EditText;
5
6import com.example.practice0924.base.BaseActivity;
7import com.example.practice0924.base.Callbacks;
8import com.example.practice0924.base.Const;
9import com.example.practice0924.base.OKHttps;
10import com.example.practice0924.databinding.ActivityLoginBinding;
11import com.example.practice0924.pojo.Msg;
12
13import java.util.HashMap;
14
15public class LoginActivity extends BaseActivity<ActivityLoginBinding> {
16 boolean isRegister = false;
17 boolean isResetPwd = false;
18
19
20 protected void init() {
21 binding.toolbar.title.setText("登录");
22 binding.toolbar.back.setVisibility(View.VISIBLE);
23 binding.toolbar.back.setOnClickListener(v -> finish());
24
25 isResetPwd = getIntent().getBooleanExtra("isResetPwd", false);
26 if (isResetPwd) {
27 binding.toolbar.title.setText("修改密码");
28 binding.eta.setHint("输入旧密码");
29 binding.ete.setHint("输入新密码");
30 binding.todo.setText("修改密码");
31 vis(binding.ete);
32 gone(binding.etb, binding.registerTip);
33 }
34
35 updateView();
36
37 binding.registerTip.setOnClickListener(v -> {
38 isRegister = !isRegister;
39 updateView();
40 });
41
42 binding.todo.setOnClickListener(v -> {
43 if (isResetPwd) {
44 if (!isEmpty(binding.eta, binding.ete)) {
45 // 重置密码
46 String oldPwd = binding.eta.getText().toString().trim();
47 String newPwd = binding.ete.getText().toString().trim();
48 HashMap<String, String> map = new HashMap<>();
49 map.put("newPassword", newPwd);
50 map.put("oldPassword", oldPwd);
51 OKHttps.put("/prod-api/api/common/user/resetPwd", map, new Callbacks<Msg>() {
52
53 protected void getRes(String res, Msg bean) {
54 toast(bean.getMsg());
55 }
56 });
57
58
59 } else {
60 toast("未输入完成内容");
61 }
62 } else if (isRegister) {
63
64 if (isEmpty(binding.eta, binding.etb, binding.etc, binding.etd)) {
65 toast("未输入完成内容");
66 return;
67 }
68
69 String phone = binding.etd.getText().toString().trim();
70 if (phone.length() != 11) {
71 toast("手机号码位数不正确");
72 return;
73 }
74
75 String password = binding.etb.getText().toString().trim();
76 String passwordRepeat = binding.etc.getText().toString().trim();
77 if (!password.equals(passwordRepeat)) {
78 toast("两次输入密码不一致");
79 return;
80 }
81
82 String userName = binding.eta.getText().toString().trim();
83 String sex = binding.male.isChecked() ? "0" : "1";
84 HashMap<String, String> map = new HashMap<>();
85 map.put("userName", userName);
86 map.put("nickName", userName);
87 map.put("password", password);
88 map.put("phonenumber", phone);
89 map.put("sex", sex);
90 OKHttps.post("/prod-api/api/register", map, new Callbacks<Msg>() {
91
92 protected void getRes(String res, Msg bean) {
93 toast(bean.getMsg());
94 }
95 });
96
97 } else {
98 if (isEmpty(binding.eta, binding.etb)) {
99 toast("未输入完成内容");
100 return;
101 }
102
103 String password = binding.etb.getText().toString().trim();
104 String userName = binding.eta.getText().toString().trim();
105
106 HashMap<String, String> map = new HashMap<>();
107 map.put("username", userName);
108 map.put("password", password);
109 OKHttps.post("/prod-api/api/login", map, new Callbacks<Msg>() {
110
111 protected void getRes(String res, Msg bean) {
112 toast(bean.getMsg());
113 if (bean.getCode() == 200) {
114 Const.TOKEN_VAL = bean.getToken();
115 setSp(Const.TOKEN, Const.TOKEN_VAL);
116 }
117 }
118 });
119 }
120 });
121 }
122
123 void updateView() {
124 if (!isResetPwd) {
125 if (isRegister) {
126 vis(binding.etc, binding.etd, binding.sexSelector);
127 } else {
128 gone(binding.etc, binding.etd, binding.sexSelector);
129 }
130 binding.toolbar.title.setText(isRegister ? "注册" : "登录");
131 binding.registerTip.setText(isRegister ? "登录账号" : "注册账号");
132 binding.todo.setText(isRegister ? "注册" : "登录");
133 }
134 }
135
136 boolean isEmpty(EditText... et) {
137 for (EditText e : et) {
138 if (TextUtils.isEmpty(e.getText().toString().trim())) {
139 return true;
140 }
141 }
142 return false;
143 }
144
145 void vis(View... views) {
146 for (View v : views) {
147 if (v instanceof EditText) {
148 ((View) v.getParent()).setVisibility(View.VISIBLE);
149 } else {
150 v.setVisibility(View.VISIBLE);
151 }
152 }
153 }
154
155 void gone(View... views) {
156 for (View v : views) {
157 if (v instanceof EditText) {
158 ((View) v.getParent()).setVisibility(View.GONE);
159 } else {
160 v.setVisibility(View.GONE);
161 }
162 }
163 }
164}
编写完页面切换逻辑加上请求即可,注意登录后应该关闭当前activity保存token
xxxxxxxxxx
1711package com.example.practice0924.page;
2
3import android.text.TextUtils;
4import android.view.View;
5import android.widget.EditText;
6
7import com.example.practice0924.base.BaseActivity;
8import com.example.practice0924.base.Callbacks;
9import com.example.practice0924.base.Const;
10import com.example.practice0924.base.OKHttps;
11import com.example.practice0924.databinding.ActivityLoginBinding;
12import com.example.practice0924.pojo.Msg;
13
14import java.util.HashMap;
15
16public class LoginActivity extends BaseActivity<ActivityLoginBinding> {
17 boolean isRegister = false;
18 boolean isResetPwd = false;
19
20
21 protected void init() {
22 binding.toolbar.title.setText("登录");
23 binding.toolbar.back.setVisibility(View.VISIBLE);
24 binding.toolbar.back.setOnClickListener(v -> finish());
25
26 isResetPwd = getIntent().getBooleanExtra("isResetPwd", false);
27 if (isResetPwd) {
28 binding.toolbar.title.setText("修改密码");
29 binding.eta.setHint("输入旧密码");
30 binding.ete.setHint("输入新密码");
31 binding.todo.setText("修改密码");
32 vis(binding.ete);
33 gone(binding.etb, binding.registerTip);
34 }
35
36 updateView();
37
38 binding.registerTip.setOnClickListener(v -> {
39 isRegister = !isRegister;
40 updateView();
41 });
42
43 binding.todo.setOnClickListener(v -> {
44 if (isResetPwd) {
45 if (!isEmpty(binding.eta, binding.ete)) {
46 // 重置密码
47 String oldPwd = binding.eta.getText().toString().trim();
48 String newPwd = binding.ete.getText().toString().trim();
49 HashMap<String, String> map = new HashMap<>();
50 map.put("newPassword", newPwd);
51 map.put("oldPassword", oldPwd);
52 OKHttps.put("/prod-api/api/common/user/resetPwd", map, new Callbacks<Msg>() {
53
54 protected void getRes(String res, Msg bean) {
55 toast(bean.getMsg());
56 if(bean.getCode()==200){
57 TOKEN_VAL = "";
58 setSp(TOKEN,TOKEN_VAL);
59 finish();
60 }
61 }
62 });
63
64
65 } else {
66 toast("未输入完成内容");
67 }
68 } else if (isRegister) {
69
70 if (isEmpty(binding.eta, binding.etb, binding.etc, binding.etd)) {
71 toast("未输入完成内容");
72 return;
73 }
74
75 String phone = binding.etd.getText().toString().trim();
76 if (phone.length() != 11) {
77 toast("手机号码位数不正确");
78 return;
79 }
80
81 String password = binding.etb.getText().toString().trim();
82 String passwordRepeat = binding.etc.getText().toString().trim();
83 if (!password.equals(passwordRepeat)) {
84 toast("两次输入密码不一致");
85 return;
86 }
87
88 String userName = binding.eta.getText().toString().trim();
89 String sex = binding.male.isChecked() ? "0" : "1";
90 HashMap<String, String> map = new HashMap<>();
91 map.put("userName", userName);
92 map.put("password", password);
93 map.put("phonenumber", phone);
94 map.put("sex", sex);
95 OKHttps.post("/prod-api/api/register", map, new Callbacks<Msg>() {
96
97 protected void getRes(String res, Msg bean) {
98 toast(bean.getMsg());
99 }
100 });
101
102 } else {
103 if (isEmpty(binding.eta, binding.etb)) {
104 toast("未输入完成内容");
105 return;
106 }
107
108 String password = binding.etb.getText().toString().trim();
109 String userName = binding.eta.getText().toString().trim();
110
111 HashMap<String, String> map = new HashMap<>();
112 map.put("username", userName);
113 map.put("password", password);
114 OKHttps.post("/prod-api/api/login", map, new Callbacks<Msg>() {
115
116 protected void getRes(String res, Msg bean) {
117 toast(bean.getMsg());
118 if (bean.getCode() == 200) {
119 Const.TOKEN_VAL = bean.getToken();
120 setSp(Const.TOKEN, Const.TOKEN_VAL);
121 toast("登陆成功");
122 finish();
123 }
124 }
125 });
126 }
127 });
128 }
129
130 void updateView() {
131 if (!isResetPwd) {
132 if (isRegister) {
133 vis(binding.etc, binding.etd, binding.sexSelector);
134 } else {
135 gone(binding.etc, binding.etd, binding.sexSelector);
136 }
137 binding.toolbar.title.setText(isRegister ? "注册" : "登录");
138 binding.registerTip.setText(isRegister ? "登录账号" : "注册账号");
139 binding.todo.setText(isRegister ? "注册" : "登录");
140 }
141 }
142
143 boolean isEmpty(EditText... et) {
144 for (EditText e : et) {
145 if (TextUtils.isEmpty(e.getText().toString().trim())) {
146 return true;
147 }
148 }
149 return false;
150 }
151
152 void vis(View... views) {
153 for (View v : views) {
154 if (v instanceof EditText) {
155 ((View) v.getParent()).setVisibility(View.VISIBLE);
156 } else {
157 v.setVisibility(View.VISIBLE);
158 }
159 }
160 }
161
162 void gone(View... views) {
163 for (View v : views) {
164 if (v instanceof EditText) {
165 ((View) v.getParent()).setVisibility(View.GONE);
166 } else {
167 v.setVisibility(View.GONE);
168 }
169 }
170 }
171}
因为这里文件上传接口测试失败,直接显示一个弹窗表示可以选择相册图片或拍摄就可以了
先为每一个按钮添加点击事件,且判断登录状态下才能打开
此处回到fragment会测试获取用户信息
测试接口生成bean然后onResume下通过token获取用户信息 顺便为所有按钮添加一个跳转页面
xxxxxxxxxx
871public class PerFragment extends BaseFragment<FragmentPerBinding> {
2 boolean isLogin;
3
4 protected void init() {
5 binding.perMsg.setOnClickListener(v->{
6 if(isLogin){
7 jump(InfoActivity.class);
8 }else{
9 toast("未登录");
10 }
11 });
12 binding.orderList.setOnClickListener(v->{
13 if(isLogin){
14 jump(OrderListActivity.class);
15 }else{
16 toast("未登录");
17 }
18 });
19 binding.feedback.setOnClickListener(v->{
20 if(isLogin){
21 jump(FeedbackActivity.class);
22 }else{
23 toast("未登录");
24 }
25 });
26 binding.pwdUpdate.setOnClickListener(v->{
27 if(isLogin){
28 Intent intent = new Intent(requireActivity(), LoginActivity.class);
29 intent.putExtra("isResetPwd", true);
30 requireActivity().startActivity(intent);
31 }else{
32 toast("未登录");
33 }
34 });
35 }
36 void updateView(){
37 binding.switchStatus.setText(isLogin?"退出":"登录");
38 binding.switchStatus.setOnClickListener(v->{
39
40 if(!isLogin){
41 jump(LoginActivity.class);
42 }else{
43 isLogin = false;
44 TOKEN_VAL = "";
45 toast("退出登录");
46 setSp(TOKEN,TOKEN_VAL);
47 updateView();
48 }
49 });
50 if(isLogin){
51 OKHttps.get("/prod-api/api/common/user/getInfo", new Callbacks<UserInfo>(){
52
53 protected void getRes(String res, UserInfo bean) {
54 if(bean.getCode()!=200){
55 toast("登陆失败");
56 return;
57 }
58 runOnUi(()->{
59 UserInfo.UserBean user = bean.getUser();
60 binding.balance.setText("¥"+user.getBalance());
61 if(user.getAvatar().contains("/profile/")){
62 user.setAvatar(URL+user.getAvatar());
63 }
64 Glide.with(requireActivity()).load(user.getAvatar()).into(binding.head);
65 String nickName = user.getNickName();
66 if(TextUtils.isEmpty(nickName.trim())){
67 nickName = user.getUserName();
68 }
69 binding.nickName.setText(nickName);
70 });
71 }
72 });
73 }else{
74 binding.balance.setText("未登录");
75 binding.head.setImageResource(R.mipmap.icon);
76 binding.nickName.setText("未登录");
77 }
78 }
79
80 public void onResume() {
81 super.onResume();
82 TOKEN_VAL = getSP(TOKEN);
83 isLogin = !TextUtils.isEmpty(TOKEN_VAL.trim());
84 updateView();
85 }
86}
87
然后编写个人信息的界面布局
xxxxxxxxxx
1901
2<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 android:layout_width="match_parent"
5 android:layout_height="match_parent"
6 android:background="#eeeeee"
7 android:orientation="vertical">
8
9 <include
10 android:id="@+id/toolbar"
11 layout="@layout/toolbar" />
12
13 <LinearLayout
14 android:layout_width="match_parent"
15 android:layout_height="match_parent"
16 android:orientation="vertical">
17
18 <RelativeLayout
19 android:id="@+id/headSetUp"
20 android:layout_width="match_parent"
21 android:layout_height="wrap_content"
22 android:background="?android:selectableItemBackground">
23
24 <ImageView
25 android:id="@+id/head"
26 android:layout_width="64dp"
27 android:layout_height="64dp"
28 android:layout_alignParentEnd="true"
29 android:layout_margin="8dp"
30 android:scaleType="fitXY"
31 android:src="@mipmap/icon" />
32
33 <TextView
34 android:layout_width="wrap_content"
35 android:layout_height="wrap_content"
36 android:layout_centerVertical="true"
37 android:layout_margin="8dp"
38 android:maxLines="1"
39 android:text="设置头像"
40 android:textSize="18sp" />
41 </RelativeLayout>
42
43 <View
44 android:layout_width="match_parent"
45 android:layout_height="1dp"
46 android:background="#ccc" />
47
48 <RelativeLayout
49 android:layout_width="match_parent"
50 android:layout_height="wrap_content"
51 android:background="?android:selectableItemBackground">
52
53 <EditText
54 android:id="@+id/nickName"
55 android:layout_width="256dp"
56 android:layout_height="wrap_content"
57 android:layout_alignParentEnd="true"
58 android:layout_margin="8dp"
59 android:hint="昵称,默认被设置为用户名" />
60
61 <TextView
62 android:layout_width="wrap_content"
63 android:layout_height="wrap_content"
64 android:layout_centerVertical="true"
65 android:layout_margin="8dp"
66 android:maxLines="1"
67 android:text="昵称"
68 android:textSize="18sp" />
69 </RelativeLayout>
70
71 <View
72 android:layout_width="match_parent"
73 android:layout_height="1dp"
74 android:background="#ccc" />
75
76 <RelativeLayout
77 android:layout_width="match_parent"
78 android:layout_height="wrap_content"
79 android:background="?android:selectableItemBackground">
80
81 <EditText
82 android:id="@+id/phone"
83 android:layout_width="256dp"
84 android:layout_height="wrap_content"
85 android:layout_alignParentEnd="true"
86 android:layout_margin="8dp"
87 android:hint="11位手机号码"
88 android:inputType="phone" />
89
90 <TextView
91 android:layout_width="wrap_content"
92 android:layout_height="wrap_content"
93 android:layout_centerVertical="true"
94 android:layout_margin="8dp"
95 android:maxLines="1"
96 android:text="手机号码"
97 android:textSize="18sp" />
98 </RelativeLayout>
99
100 <View
101 android:layout_width="match_parent"
102 android:layout_height="1dp"
103 android:background="#ccc" />
104
105 <View
106 android:layout_width="match_parent"
107 android:layout_height="1dp"
108 android:background="#ccc" />
109
110 <RelativeLayout
111 android:layout_width="match_parent"
112 android:layout_height="wrap_content"
113 android:background="?android:selectableItemBackground">
114
115 <RadioGroup
116 android:layout_width="wrap_content"
117 android:layout_height="wrap_content"
118 android:layout_alignParentEnd="true"
119 android:orientation="horizontal">
120
121 <RadioButton
122 android:id="@+id/male"
123 android:layout_width="wrap_content"
124 android:layout_height="wrap_content"
125 android:checked="true"
126 android:text="男" />
127
128 <RadioButton
129 android:id="@+id/female"
130 android:layout_width="wrap_content"
131 android:layout_height="wrap_content"
132 android:text="女" />
133 </RadioGroup>
134
135 <TextView
136 android:layout_width="wrap_content"
137 android:layout_height="wrap_content"
138 android:layout_centerVertical="true"
139 android:layout_margin="8dp"
140 android:maxLines="1"
141 android:text="性别"
142 android:textSize="18sp" />
143 </RelativeLayout>
144
145 <View
146 android:layout_width="match_parent"
147 android:layout_height="1dp"
148 android:background="#ccc" />
149
150 <RelativeLayout
151 android:layout_width="match_parent"
152 android:layout_height="wrap_content"
153 android:background="?android:selectableItemBackground">
154
155 <TextView
156 android:id="@+id/idCard"
157 android:layout_width="wrap_content"
158 android:layout_height="wrap_content"
159 android:layout_alignParentEnd="true"
160 android:layout_centerVertical="true"
161 android:layout_margin="8dp"
162 android:maxLines="1"
163 android:text="44************2117"
164 android:textSize="18sp" />
165
166 <TextView
167 android:layout_width="wrap_content"
168 android:layout_height="wrap_content"
169 android:layout_centerVertical="true"
170 android:layout_margin="8dp"
171 android:maxLines="1"
172 android:text="证件号码"
173 android:textSize="18sp" />
174 </RelativeLayout>
175
176 <View
177 android:layout_width="match_parent"
178 android:layout_height="1dp"
179 android:background="#ccc" />
180
181 <Button
182 android:id="@+id/update"
183 android:layout_width="match_parent"
184 android:layout_height="60dp"
185 android:layout_marginHorizontal="8dp"
186 android:text="修改"
187 android:textSize="20sp" />
188 </LinearLayout>
189
190</LinearLayout>
进入Activity先获取信息设置上去,然后监听按钮点击判空输入,提交修改
xxxxxxxxxx
1021
2import android.app.AlertDialog;
3import android.text.TextUtils;
4import android.view.View;
5import android.widget.EditText;
6
7
8import com.bumptech.glide.Glide;
9import com.example.practice0924.R;
10import com.example.practice0924.base.BaseActivity;
11import com.example.practice0924.base.Callbacks;
12import com.example.practice0924.base.OKHttps;
13import com.example.practice0924.databinding.ActivityInfoBinding;
14import com.example.practice0924.pojo.Msg;
15import com.example.practice0924.pojo.UserInfo;
16
17import java.util.HashMap;
18
19import static com.example.practice0924.base.Const.URL;
20
21public class InfoActivity extends BaseActivity<ActivityInfoBinding> {
22
23
24 protected void init() {
25 binding.toolbar.title.setText("个人信息");
26 binding.toolbar.back.setVisibility(View.VISIBLE);
27 binding.toolbar.back.setOnClickListener(v -> finish());
28
29 OKHttps.get("/prod-api/api/common/user/getInfo", new Callbacks<UserInfo>() {
30
31 protected void getRes(String res, UserInfo bean) {
32 if (bean.getCode() != 200) {
33 toast("登陆失败");
34 return;
35 }
36 runOnUiThread(() -> {
37 UserInfo.UserBean user = bean.getUser();
38 if (user.getAvatar().contains("/profile/")) {
39 user.setAvatar(URL + user.getAvatar());
40 }
41 Glide.with(InfoActivity.this).load(user.getAvatar()).into(binding.head);
42 String nickName = user.getNickName();
43 if (TextUtils.isEmpty(nickName.trim())) {
44 nickName = user.getUserName();
45 }
46 binding.nickName.setText(nickName);
47 String phone = user.getPhonenumber();
48 binding.phone.setText(phone);
49 String idCard = user.getIdCard();
50 if (idCard.length() > 6) {
51 idCard = idCard.substring(0, 2) + "************" + idCard.substring(idCard.length() - 4);
52 }
53 binding.idCard.setText(idCard);
54 boolean male = user.getSex().equals("0");
55 if (male) {
56 binding.male.setChecked(true);
57 } else {
58 binding.female.setChecked(true);
59 }
60
61 });
62 }
63 });
64
65 binding.headSetUp.setOnClickListener(v -> {
66 new AlertDialog.Builder(InfoActivity.this)
67 .setTitle("上传图片")
68 .setNegativeButton("相册选择", null)
69 .setPositiveButton("相机拍摄", null)
70 .setNegativeButton("取消", null)
71 .create().show();
72 });
73 binding.update.setOnClickListener(v -> {
74 if (!isEmpty(binding.nickName, binding.phone)) {
75 String nickName = binding.nickName.getText().toString();
76 String phone = binding.phone.getText().toString();
77 String sex = binding.female.isChecked() ? "1" : "0";
78 HashMap<String, String> map = new HashMap<>();
79 map.put("nickName", nickName);
80 map.put("phonenumber", phone);
81 map.put("sex", sex);
82 OKHttps.put("/prod-api/api/common/user", map, new Callbacks<Msg>() {
83
84 protected void getRes(String res, Msg bean) {
85 toast(bean.getMsg());
86 }
87 });
88 } else {
89 toast("未输入完成");
90 }
91 });
92 }
93
94 boolean isEmpty(EditText... et) {
95 for (EditText e : et) {
96 if (TextUtils.isEmpty(e.getText().toString().trim())) {
97 return true;
98 }
99 }
100 return false;
101 }
102}
用一个TextInputLayout做一个字数限制
xxxxxxxxxx
341<?xml version="1.0" encoding="utf-8"?>
2<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 android:orientation="vertical"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:background="#eeeeee">
8
9 <include
10 android:id="@+id/toolbar"
11 layout="@layout/toolbar" />
12 <com.google.android.material.textfield.TextInputLayout
13 android:layout_width="match_parent"
14 android:layout_height="150dp"
15 app:counterEnabled="true"
16 android:layout_margin="8dp"
17 style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
18 app:counterMaxLength="150">
19 <com.google.android.material.textfield.TextInputEditText
20 android:layout_width="match_parent"
21 android:layout_height="match_parent"
22 android:maxLength="150"
23 android:gravity="start"
24 android:id="@+id/feedback"
25 android:hint="意见反馈"
26 />
27 </com.google.android.material.textfield.TextInputLayout>
28<Button
29 android:layout_width="match_parent"
30 android:layout_height="wrap_content"
31 android:layout_margin="8dp"
32 android:text="提交"
33 android:id="@+id/submit"/>
34</LinearLayout>
直接监听按钮发个post请求
xxxxxxxxxx
381
2import android.text.TextUtils;
3import android.view.View;
4
5import com.example.practice0924.base.BaseActivity;
6import com.example.practice0924.base.Callbacks;
7import com.example.practice0924.base.OKHttps;
8import com.example.practice0924.databinding.ActivityFeedbackBinding;
9import com.example.practice0924.pojo.Msg;
10
11import java.util.HashMap;
12
13public class FeedbackActivity extends BaseActivity<ActivityFeedbackBinding> {
14
15
16 protected void init() {
17 binding.toolbar.title.setText("意见反馈");
18 binding.toolbar.back.setVisibility(View.VISIBLE);
19 binding.toolbar.back.setOnClickListener(v->finish());
20 binding.submit.setOnClickListener(v -> {
21 String inp = binding.feedback.getText().toString();
22 if(TextUtils.isEmpty(inp.trim())){
23 toast("输入内容为空");
24 return;
25 }
26 1
27 HashMap<String, String> map = new HashMap<>();
28 map.put("title", "意见反馈");
29 map.put("content", inp);
30 OKHttps.post("/prod-api/api/common/feedback", map, new Callbacks<Msg>() {
31
32 protected void getRes(String res, Msg bean) {
33 toast(bean.getMsg());
34 }
35 });
36 });
37 }
38}
因为没打算做智慧巴士啥的,这里直接显示一个包含两个item(已支付,未支付)的tabLayout敷衍一下
xxxxxxxxxx
361
2<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 android:orientation="vertical"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:background="#eeeeee">
8
9 <include
10 android:id="@+id/toolbar"
11 layout="@layout/toolbar" />
12 <LinearLayout
13 android:layout_width="match_parent"
14 android:layout_height="match_parent"
15 android:orientation="vertical"
16 >
17 <com.google.android.material.tabs.TabLayout
18 android:id="@+id/tabLayout"
19 android:layout_width="match_parent"
20 android:layout_height="56dp"
21 android:background="#fff"
22 app:tabMode="fixed">
23
24 <com.google.android.material.tabs.TabItem
25 android:layout_width="wrap_content"
26 android:layout_height="wrap_content"
27 android:text="已支付" />
28
29 <com.google.android.material.tabs.TabItem
30 android:layout_width="wrap_content"
31 android:layout_height="wrap_content"
32 android:text="未支付" />
33 </com.google.android.material.tabs.TabLayout>
34 </LinearLayout>
35
36</LinearLayout>
-> Build -> Generate Signed Bundle / APK
选择打包APK
选择CreateNew,选择存储位置,密码,和随便填一个校验
然后点Next一键打包,选择release并且勾选两个签名方案
打包release包耗时比较久,至少预留半小时打包和测试
release/app-release.apk
提交前记得查看打包命名要求,这里默认改成SmartCity.apk