多级目录功能的设计与开发
- 演示效果
- 需求描述
- 开发预计
- 前端
- 后端
- 前端开发
- 多级目录的UI小框架
- 前端xml
- 前端代码---本地demo版
- 前端代码---服务器版
- 逻辑说明
- 后端开发
- 表设计
- 书写接口
演示效果
需求描述
根据需求,为用户展示多级目录(目前设计的为4级目录),并在选择了指定目录后显示详情页面,供用户点赞,查看,上传,标记等操作
开发预计
前端
- 每一页显示一个层级/目录的内容,包括标题,img,以及可能会有的对目录的描述
- 进入到最终目录后点击进入详情页的查看,详情页包括最终目录所属的前面所有层级/目录的标题内容,以及最终目录的详细内容
- 详细内容除2描述以外还需要展示类似评论的局部功能(可进行点赞和取消点赞操作),对详情页的标记功能,该目录是否有帮助功能,以及添加对这个详情页自己的想法的功能
- 详情页的每个操作尽量提示用户操作结果
- UI尚未作出要求
后端
- 多级表设计
- 尽量用最少的接口进行与服务器的交流
前端开发
多级目录的UI小框架
插一嘴 我的实现流程是在应用现有的activity的view上动态添加新的view,也就是每一级目录新增一个view
我在activity和view之间犹豫考量了一下, 最终认为actviity的形式有点小题大做,毕竟也只是显示一个view,上面显示一些基本的信息,顶多最终的详情页可能值一个activity,但是在新建5个activity和用一个view进行4个的复件加一个单独处理的详情页的view之间,我选择了后者,毕竟光是数据在不同activity之间的传输就已经够我的脑子宕机好几天了
回到正题,我用的是别的大佬写好的关于动态创建一个目录页面view的代码,然后进行魔改的方式,写了一个管理器来动态创建view
直接上源码
public class CustomViewMgr {
//创建一个容器用于存放view,进行管理
// List<View> views;
// ViewGroup curView;
protected LayoutInflater inflater;
public CustomViewMgr(Context context) {
//初始化
inflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);
}
public LayoutInflater getInflater(){
return this.inflater;
}
/**
* 根据layout和对应的view找到需要新生成的view,为了方便拓展接收和返回都设置为了object
*
* @param oldView 原来在显示的view
* @param layoutId 你新的xml的id
* @param newViewId 你新的xml对应的viewId
* @return Object
*/
public Object getNewView(View oldView, int layoutId, int newViewId) {
if (oldView != null) {
oldView.setVisibility(View.GONE);
}
//获得需要新显示的layout
ConstraintLayout constraintLayout2 = (ConstraintLayout) inflater.inflate(layoutId, null);
//根据layout获得需要的view
//添加到view容器中
return constraintLayout2.findViewById(newViewId);
}
public void refreshRecycleView(RecyclerView recyclerView, NewsItemAdapter temAdapter,ViewGroup curView) {
//这一步是把绑定好数据的适配器添加到视图上
recyclerView.setAdapter(temAdapter);
//这一步是清除要展示的view的父级,否则会闪退报错
ViewGroup parent_tem = (ViewGroup) recyclerView.getParent();
parent_tem.removeView(recyclerView);
//最后再把视图和配套的数据添加到现有的view中
curView.addView(recyclerView);
}
}
这个CustomViewMgr
类其实很简单,主要是用来找到位于其他xml的view,比如说我想在某个view上加入其他view里的某个组件(按钮,列表之类的),通过这个类的getNewView
,第一个参数可以默认填null,我这里主要是图省事,希望在每创建新的view的时候可以自动隐藏之前的view就写在了这里面,主要是后面两个,第一个是layoutid,第二个是你需要新建的view的id,就可以返回你需要的view了,为什么这么麻烦,因为如果你不在对应的layout下找你的view,系统会默认在当前的view下找你的这个view,如果刚你你新建的view是在当前的view下,那就没事,但如果不在的话就会直接报错闪退,提示你当前的view是没有你要的view的
至于这个CustomViewMgr
类的第二个refreshRecycleView方法主要是针对RecyclerView
这个view的,如果不在创建之前把他的父级目录移除然后新增到现在的view下的话,他会报错异常说你需要先移除RecyclerView
的父级目录才能新增,其他的view不知道有没有这个问题,以后遇到再说吧
前端xml
在res/anim下创建有关动画的xml
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:animation="@anim/slide_in_left"
android:animationOrder="normal"
android:delay="15%">
</layoutAnimation>
在res/drawable下创建分割水平组件的xml
<?xml version="1.0" encoding="utf-8"?>
<shape android:shape="rectangle"
xmlns:android="http://schemas.android.com/apk/res/android" >
<size android:height="1dp" android:width="100dp"/>
</shape>
在res/layout下创建详情页面的xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ececed"
tools:context=".TestActivity">
//这个LineraLayout是详情页的view
<LinearLayout
android:id="@+id/showNewView"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#fff"
android:gravity="center_horizontal"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/common_15dp"
android:divider="@drawable/divider"
android:gravity="center"
android:orientation="horizontal"
android:showDividers="middle">
<LinearLayout
android:id="@+id/solve_btn"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="50sp"
android:layout_height="50sp"
android:src="@drawable/pic" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="已解决"/>
</LinearLayout>
<LinearLayout
android:id="@+id/mark_btn"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="50sp"
android:layout_height="50sp"
android:src="@drawable/pic" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="标记"/>
</LinearLayout>
<LinearLayout
android:id="@+id/add_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="50sp"
android:layout_height="50sp"
android:src="@drawable/pic" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:text="我的想法"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/menu1_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:text="测试"
android:textColor="#000"
android:textSize="30dp" />
<TextView
android:id="@+id/menu2_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="测试"
android:textColor="#000"
android:textSize="20dp" />
<TextView
android:id="@+id/menu3_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:text="测试"
android:textColor="#000"
android:textSize="20dp" />
<TextView
android:id="@+id/menu4_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="测试"
android:textColor="#000"
android:textSize="20dp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal|center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/new_view_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:text="测试"
android:textAlignment="center"
android:textColor="#000"
android:textSize="20dp"
android:textStyle="bold" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/showCommentView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager">
</androidx.recyclerview.widget.RecyclerView>
</LinearLayout>
//------------这个RecyclerView就是显示每级目录的view
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/menuRecycleView"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipToPadding="false"
android:layoutAnimation="@anim/layout_slide_in_left"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>
如果你这里报异常说找不到RecyclerView组件的话
需要在对应的build.gradle
下写入
implementation 'androidx.recyclerview:recyclerview:1.0.0'
然后sync(androidstudio右上角的海豚)一下
上面的这个xml既包括了详情页的view,也包括了显示每级目录的view,当然你可以再创建一个xml来单独存放每级目录的view,这里我就是lazy了而已…
在res/layout下创建关于每个目录中的item的xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
app:cardCornerRadius="4dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:layout_width="20sp"
android:layout_height="20sp"
android:src="@mipmap/ic_launcher"
/>
<TextView
android:id="@+id/textTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="test "
android:paddingLeft="10sp"
android:textColor="#212121"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:id="@+id/textDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#757575"
android:textIsSelectable="true"
android:textSize="16sp"/>
<TextView
android:id="@+id/textZan"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#757575"
android:text=""
android:textAlignment="textEnd"
android:textSize="16sp"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
如果你这里提示没有CardView
的,和上面的RecyclerView
一样,在对应的build.grale
加上下面这个
implementation 'androidx.cardview:cardview:1.0.0'
然后sync一下
前端代码—本地demo版
然后是才终于轮到activity
新建一个activity类,继承Activity
因为是demo阶段,所以没有涉及到服务器,下面的数据部分是手动添加的假数据
package com.yeyupiaoling.testvad;
import android.app.Activity;
import android.app.AlertDialog;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.recyclerview.widget.RecyclerView;
import com.yeyupiaoling.testvad.CustomView.CustomViewMgr;
import java.util.ArrayList;
import java.util.List;
public class TestActivity extends Activity {
int menu1 = 0;
int menu2 = 0;
int menu3 = 0;
int menu4 = 0;
LayoutInflater inflater;
ConstraintLayout parent;
//创建一个容器用于存放view,进行管理
List<View> views;
ViewGroup curView;
CustomViewMgr customViewMgr;
// 编辑界面
LinearLayout edit_view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 初始化
views = new ArrayList<>();
edit_view = null;
setContentView(R.layout.activity_test);
curView = findViewById(R.id.activity_main);
customViewMgr = new CustomViewMgr(getApplicationContext());
RecyclerView newRecyclerView = (RecyclerView) customViewMgr.getNewView(null, R.layout.activity_menu1, R.id.menuRecycleView);
List<NewsItem> newsItems = new ArrayList<>();
for (int i = 0; i < 15; i++) {
newsItems.add(new NewsItem("一级目录" + i, "this is a news about pure doing a new function in android studio"));
}
NewsItemAdapter adapter = new NewsItemAdapter(newsItems);
adapter.setOnItemClickListener(new NewsItemAdapter.OnItemClickListener() {
@Override
public void onItemClick(int postion, View view) {
menu1 = postion;
//这一步是找到需要展示的view
RecyclerView recycleView_tem = (RecyclerView) customViewMgr.getNewView(newRecyclerView, R.layout.activity_menu1, R.id.menuRecycleView);
List<NewsItem> newsItems = new ArrayList<>();
for (int i = 0; i < 15; i++) {
newsItems.add(new NewsItem("二级目录" + i, "this is a news about pure doing a new function in android studio"));
}
NewsItemAdapter adapter_tem = new NewsItemAdapter(newsItems);
adapter_tem.setOnItemClickListener(new NewsItemAdapter.OnItemClickListener() {
@Override
public void onItemClick(int postion, View view) {
menu2 = postion;
// parent.removeView(newRecyclerView1);
// ConstraintLayout constraintLayout3 = (ConstraintLayout)inflater.inflate(R.layout.activity_menu2,null);
// RecyclerView newRecyclerView1 = (RecyclerView) constraintLayout3.findViewById(R.id.menu2RecycleView);
RecyclerView recycleView_tem2 = (RecyclerView) customViewMgr.getNewView(recycleView_tem, R.layout.activity_menu1, R.id.menuRecycleView);
//这一步是建立数据
List<NewsItem> newsItems = new ArrayList<>();
for (int i = 0; i < 15; i++) {
newsItems.add(new NewsItem("三级目录" + i, "this is a news about pure doing a new function in android studio"));
}
//这一步是绑定数据
NewsItemAdapter adapter1 = new NewsItemAdapter(newsItems);
//这一步是绑定点击事件
adapter1.setOnItemClickListener(new NewsItemAdapter.OnItemClickListener() {
@Override
public void onItemClick(int postion, View view) {
menu3 = postion;
RecyclerView recyclerView_tem3 = (RecyclerView) customViewMgr.getNewView(recycleView_tem2, R.layout.activity_menu1, R.id.menuRecycleView);
List<NewsItem> newsItems = new ArrayList<>();
for (int i = 0; i < 15; i++) {
newsItems.add(new NewsItem("终极目录" + i, "this is a news about pure doing a new function in android studio"));
}
//这一步是绑定数据
NewsItemAdapter adapter1 = new NewsItemAdapter(newsItems);
adapter1.setOnItemClickListener(new NewsItemAdapter.OnItemClickListener() {
@Override
public void onItemClick(int postion, View view) {
menu4 = postion;
// Button test = (Button) getNewView( recyclerView_tem3,R.layout.activity_menu1,R.id.showRecycleView);
// refreshView(test);
show_last_view();
}
});
customViewMgr.refreshRecycleView(recyclerView_tem3, adapter1, curView);
views.add(recyclerView_tem3);
}
});
customViewMgr.refreshRecycleView(recycleView_tem2, adapter1, curView);
views.add(recycleView_tem2);
}
});
customViewMgr.refreshRecycleView(recycleView_tem, adapter_tem, curView);
views.add(recycleView_tem);
}
});
customViewMgr.refreshRecycleView(newRecyclerView, adapter, curView);
views.add(newRecyclerView);
}
@Override
public void onBackPressed() {
if (views.size() > 0) {
//说明还有上一级,隐藏当前返回上一级
if (views.size() != 1) {
views.get(views.size() - 2).setVisibility(View.VISIBLE);
curView.removeView(views.get(views.size() - 1));
views.remove(views.get(views.size() - 1));
} else {
views.get(0).setVisibility(View.GONE);
views.remove(views.get(views.size() - 1));
}
} else {
super.onBackPressed();
}
}
//这个是显示详情页面的view
private void show_last_view() {
String result = " 这是学生顶撞老师的最常见原因。\n" +
" 解决方法——教师开口之前,先询问一下,调查一下,就可以避免很多师生冲突。";
views.get(views.size() - 1).setVisibility(View.GONE);
View inflate = customViewMgr.getInflater().inflate(R.layout.activity_menu1, null);
LinearLayout linearLayout = (LinearLayout) inflate.findViewById(R.id.showNewView);
ViewGroup parent = (ViewGroup) linearLayout.getParent();
parent.removeView(linearLayout);
TextView textView1 = (TextView) linearLayout.findViewById(R.id.menu1_text);
textView1.setText("纪律问题");
TextView textView2 = (TextView) linearLayout.findViewById(R.id.menu2_text);
textView2.setText("顶撞老师");
TextView textView3 = (TextView) linearLayout.findViewById(R.id.menu3_text);
textView3.setText("从教师责任的角度分析");
TextView textView4 = (TextView) linearLayout.findViewById(R.id.menu4_text);
textView4.setText("(1)老师冤枉了学生");
TextView textView = (TextView) linearLayout.findViewById(R.id.new_view_text);
textView.setText(result);
RecyclerView recyclerView = (RecyclerView) linearLayout.findViewById(R.id.showCommentView);
List<NewsItem> items = new ArrayList<>();
items.add(new NewsItem("Mr.王", "我觉得这么做会更好一点", "30"));
items.add(new NewsItem("Mr.张", "我觉得这么做会更好一点", "20"));
items.add(new NewsItem("Mr.赵", "我觉得这么做会更好一点", "10"));
items.add(new NewsItem("Mr.赵", "我觉得这么做会更好一点", "10"));
items.add(new NewsItem("Mr.赵", "我觉得这么做会更好一点", "10"));
items.add(new NewsItem("Mr.赵", "我觉得这么做会更好一点", "10"));
items.add(new NewsItem("Mr.李", "我觉得这么做会更好一点", "0"));
NewsItemAdapter adapter = new NewsItemAdapter(items);
recyclerView.setAdapter(adapter);
adapter.setOnItemClickListener(new NewsItemAdapter.OnItemClickListener() {
@Override
public void onItemClick(int postion, View view) {
AlertDialog.Builder builder = new AlertDialog.Builder(TestActivity.this);
builder.setTitle("确认");
builder.setMessage("你进行了点赞");
builder.show();
}
});
LinearLayout solve_ly = linearLayout.findViewById(R.id.solve_btn);
solve_ly.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
AlertDialog.Builder builder = new AlertDialog.Builder(TestActivity.this);
builder.setTitle("确认");
builder.setMessage("已标记为解决");
builder.show();
return false;
}
});
LinearLayout mark_ly = linearLayout.findViewById(R.id.mark_btn);
mark_ly.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
AlertDialog.Builder builder = new AlertDialog.Builder(TestActivity.this);
builder.setTitle("确认");
builder.setMessage("已标记");
builder.show();
return false;
}
});
LinearLayout add_ly = linearLayout.findViewById(R.id.add_btn);
add_ly.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
//进入编辑的view
views.get(views.size() - 1).setVisibility(View.GONE);
if (views.contains(edit_view)) {
int index = views.indexOf(views.contains(edit_view));
views.get(index).setVisibility(View.VISIBLE);
return false;
}
View inflate = customViewMgr.getInflater().inflate(R.layout.add_opinion, null);
LinearLayout linearLayout = (LinearLayout) inflate.findViewById(R.id.add_view);
ViewGroup parent1 = (ViewGroup) linearLayout.getParent();
parent1.removeView(linearLayout);
Button back_add_view = (Button) linearLayout.findViewById(R.id.back_add_view);
EditText editText = (EditText) linearLayout.findViewById(R.id.add_opinion_text);
editText.setHint(menu1 + "\n" + menu2 + "\n" + menu3 + "\n" + menu4 + "\n" + "请输入您的想法");
back_add_view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
Button upload_opinion = (Button) linearLayout.findViewById(R.id.upload_new_opinion);
upload_opinion.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
AlertDialog.Builder builder = new AlertDialog.Builder(TestActivity.this);
builder.setTitle("确认");
builder.setMessage("上传完成");
builder.show();
}
});
curView.addView(linearLayout);
edit_view = linearLayout;
views.add(linearLayout);
return false;
}
});
curView.addView(linearLayout);
views.add(linearLayout);
}
}
如果以上的都能运行成功说明demo阶段就没问题了
前端代码—服务器版
下面是和服务器有关的前端代码展示
这里说一下, 因为我们接入了腾讯的即时通信,而这个需求是需要在这里面搭建的,因此如果你看不太懂的话也很正常
我专门创建了一个继承了Dialog的CustomView
类,用来管理自定义的新建的view
逻辑说明
服务器相关的代码主要是在每一级的目录创建时需要先从服务器下拉数据,然后根据数据进行实时的view渲染和显示,涉及到一个延迟的问题,因为是自定义的所以没有写有关加载过程添加提示加载中的功能,这里只是做了一个简单的回调接口,在等待服务器响应后再执行view的生成
因为是继承了Dialog,所以存在点击view之外的界面所有的view都会消失的问题,因此需要设置setcancelable为false,然后重写一下onBackPressed这个方法
有关请求服务器的部分因为是外包搭建的框架,是用的library_commom
模块下的SubscribeUtils
进行的请求,具体没有深究原理,不是重点
请求服务器的方式萝卜青菜各有所爱
有关详情页的部分有一些我是请求了服务器之后没有重新进入,而是在本地临时存了一些数据用来记录,然后实时更新,减少一点和服务器的请求以及增加一点体验感
package com.tencent.qcloud.tim.demo.helper;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.jbb.library_common.comfig.KeyContacts;
import com.jbb.library_common.retrofit.RetrofitManager;
import com.jbb.library_common.retrofit.other.BaseBean;
import com.jbb.library_common.retrofit.other.NetListeren;
import com.jbb.library_common.retrofit.other.SubscribeUtils;
import com.jbb.library_common.utils.ToastUtil;
import com.tencent.qcloud.tim.demo.R;
import com.tencent.qcloud.tim.demo.helper.custommsg.CustomMsgChooseListeren;
import com.tencent.qcloud.tim.demo.helper.custommsg.CustomMsgDialog;
import com.tencent.qcloud.tim.demo.helper.teacherquestion.CustomViewMgr;
import com.tencent.qcloud.tim.demo.helper.teacherquestion.NewsItem;
import com.tencent.qcloud.tim.demo.helper.teacherquestion.NewsItemAdapter;
import com.tencent.qcloud.tim.demo.helper.teacherquestion.TeacherPsyMethonResBean;
import com.tencent.qcloud.tim.demo.helper.teacherquestion.TeacherQuestionResBean;
import com.tencent.qcloud.tim.demo.utils.Constants;
import com.tencent.qcloud.tim.uikit.modules.chat.ChatLayout;
import com.tencent.qcloud.tim.uikit.modules.chat.layout.input.InputLayout;
import com.tencent.qcloud.tim.uikit.service.IMApiService;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import static com.tencent.liteav.base.ContextUtils.getApplicationContext;
public class CustomVIew extends Dialog {
private Context curContext;
private ChatLayout curLayout;
private InputLayout curInputLayout;
private CustomViewMgr customViewMgr;
private List<NewsItem> curMenuInfo;
private View curView;
private int curIndex;
private int curMenu;
private List<List<NewsItem>> menuInfos;
private List<View> views;
private HashMap<Integer, NewsItem> psyDetailInfo;
private Button toMenu1, toMenu2;
public CustomVIew(Context context, ChatLayout layout, InputLayout inputLayout) {
super(context);
this.curContext = context;
this.curLayout = layout;
this.curInputLayout = inputLayout;
this.curMenuInfo = new LinkedList<>();
this.curIndex = 0;
this.curMenu = 0;
this.views = new LinkedList<>();
this.menuInfos = new LinkedList<>();
this.psyDetailInfo = new HashMap();
customViewMgr = new CustomViewMgr(context);
init();
}
private void init() {
//先弹出两个选择框,用户选择,第一个进入语料库,第二个进入新的问题心理合集功能
LayoutInflater inflater = LayoutInflater.from(curContext);
curView = inflater.inflate(R.layout.pure_diy_view, (ViewGroup) getWindow().getDecorView(), false);
LinearLayout btns = curView.findViewById(R.id.pure_show_zhixingyuku_btn);
toMenu1 = (Button) btns.findViewById(R.id.to_teacher_zhixingyuku);
toMenu2 = (Button) btns.findViewById(R.id.to_teacher_psyquestion);
toMenu1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final CustomMsgDialog dialog = new CustomMsgDialog(curContext, curInputLayout);
dialog.setListeren(new CustomMsgChooseListeren() {
@Override
public void messageChoose(String text) {
curLayout.getInputLayout().setTextInput(text);
dialog.dismiss();
}
});
dialog.showDialog();
}
});
toMenu2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//这里要隐藏两个按钮的减轻事件
toMenu1.setVisibility(View.GONE);
toMenu2.setVisibility(View.GONE);
setCancelable(false);
add_menu(1, 0, new Callback() {
@Override
public void onSuccess() {
curMenu = 1;
//添加监听事件
add_menu(2, curMenuInfo.get(curIndex).getId(), new Callback() {
@Override
public void onSuccess() {
curMenu = 2;
add_menu(3, curMenuInfo.get(curIndex).getId(), new Callback() {
@Override
public void onSuccess() {
curMenu = 3;
add_menu(4, curMenuInfo.get(curIndex).getId(), new Callback() {
@Override
public void onSuccess() {
curMenu = 4;
//进入详情页面
show_last_view();
}
@Override
public void onFail() {
}
});
}
@Override
public void onFail() {
}
});
}
@Override
public void onFail() {
}
});
}
@Override
public void onFail() {
}
});
}
});
setContentView(curView);
setCancelable(true);
Window dialogWindow = getWindow();
dialogWindow.setGravity(Gravity.CENTER);
dialogWindow.setWindowAnimations(R.style.bottomdialogAnim);
dialogWindow.setBackgroundDrawableResource(R.color.common_transparent);
WindowManager.LayoutParams lp = dialogWindow.getAttributes();
lp.width = ViewGroup.LayoutParams.MATCH_PARENT; //
lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
dialogWindow.setAttributes(lp);
}
private void getMenuInfo(int menu, int pid, final Callback callback) {
List<NewsItem> result = new ArrayList<>();
SubscribeUtils.subscribe(RetrofitManager.getInstance().getService(IMApiService.class)
.loadTeacherQuestionList(KeyContacts.token, menu, pid), TeacherQuestionResBean.class, new NetListeren<TeacherQuestionResBean>() {
@Override
public void onSuccess(TeacherQuestionResBean teacherQuestionResBean) {
if (teacherQuestionResBean.getData().size() <= 0) {
ToastUtil.showCustomToast("加载出现异常,请重启");
return;
}
curMenuInfo = teacherQuestionResBean.getData();
menuInfos.add(curMenuInfo);
callback.onSuccess();
}
@Override
public void onError(Exception e) {
callback.onFail();
}
});
}
public interface Callback {
void onSuccess();
void onFail();
}
private void add_menu(final int menu, final int pid, final Callback callback) {
getMenuInfo(menu, pid, new Callback() {
@Override
public void onSuccess() {
// setCancelable(false);
View oldView = null;
if (views.size() != 0) {
//说明有遗留的view,需要隐藏
oldView = views.get(views.size() - 1);
}
final RecyclerView newRecyclerView = (RecyclerView) customViewMgr.getNewView(oldView, R.layout.activity_menu1, R.id.menuRecycleView);
NewsItemAdapter adapter = new NewsItemAdapter(curMenuInfo);
adapter.setOnItemClickListener(new NewsItemAdapter.OnItemClickListener() {
@Override
public void onItemClick(int postion, View view) {
curIndex = postion;
System.out.println(curMenuInfo.get(postion));
psyDetailInfo.put(menu, curMenuInfo.get(postion));
callback.onSuccess();
}
});
customViewMgr.refreshRecycleView(newRecyclerView, adapter, (ViewGroup) curView);
views.add(newRecyclerView);
}
@Override
public void onFail() {
ToastUtil.showCustomToast("加载失败~");
// setCancelable(true);
}
});
}
private int final_menu = 4;
@Override
public void onBackPressed() {
if (views.size() > 0) {
//说明还有上一级,隐藏当前返回上一级
if (views.size() != 1) {
views.get(views.size() - 2).setVisibility(View.VISIBLE);
ViewGroup tem = (ViewGroup) curView;
tem.removeView(views.get(views.size() - 1));
views.remove(views.get(views.size() - 1));
curMenu--;
if (curMenu < final_menu) {
menuInfos.remove(menuInfos.size() - 1);
curMenuInfo = menuInfos.get(menuInfos.size() - 1);
}
} else {
views.get(0).setVisibility(View.GONE);
views.remove(views.get(views.size() - 1));
toMenu1.setVisibility(View.VISIBLE);
toMenu2.setVisibility(View.VISIBLE);
setCancelable(true);
}
} else {
super.onBackPressed();
}
}
private void show_last_view() {
curMenu = 5;
List<NewsItem> items = new ArrayList<>();
// items.add(new NewsItem("Mr.王", "我觉得这么做会更好一点", "30"));
SubscribeUtils.subscribe(RetrofitManager.getInstance().getService(IMApiService.class)
.getTeacherPsyMethodInfo(KeyContacts.token, psyDetailInfo.get(4).getId(),Constants.CUR_CHAT_ID), TeacherPsyMethonResBean.class
, new NetListeren<TeacherPsyMethonResBean>() {
@Override
public void onSuccess(TeacherPsyMethonResBean teacherPsyMethonResBean) {
//这是解决方法
final JSONArray comments = (JSONArray) teacherPsyMethonResBean.getData().get("methods");
final List<NewsItem> comment = JSONObject.parseArray(comments.toJSONString(), NewsItem.class);
//这是解决方法对应用户是否点赞
final JSONArray isZans = (JSONArray) teacherPsyMethonResBean.getData().get("isZan");
final List<HashMap> isZan = JSONObject.parseArray(isZans.toJSONString(), HashMap.class);
//这是查看当前目录用户是否进行了标记
int status = Integer.parseInt(teacherPsyMethonResBean.getData().get("status").toString());
//这是查看当前目录用户是否觉得有效
int is_effect = Integer.parseInt(teacherPsyMethonResBean.getData().get("is_effect").toString());
final int[] tem_info = new int[]{status,is_effect};
if (comment.size() <= 0) {
ToastUtil.showCustomToast("期待您的填写");
}
if (isZan.size() != 0) {
for (int i = 0; i < comment.size(); i++) {
for (int j = 0; j < isZan.size(); j++) {
if (isZan.get(j).get("zid").equals(comment.get(i).getId() + "")) {
comment.get(i).setZan(true);
}
}
}
}
String result = Objects.requireNonNull(psyDetailInfo.get(4)).getContent();
views.get(views.size() - 1).setVisibility(View.GONE);
View inflate = customViewMgr.getInflater().inflate(R.layout.activity_menu1, (ViewGroup) getWindow().getDecorView(), false);
LinearLayout linearLayout = (LinearLayout) inflate.findViewById(R.id.showNewView);
ViewGroup parent = (ViewGroup) linearLayout.getParent();
parent.removeView(linearLayout);
TextView textView1 = (TextView) linearLayout.findViewById(R.id.menu1_text);
textView1.setText(Objects.requireNonNull(psyDetailInfo.get(1)).getTitle());
TextView textView2 = (TextView) linearLayout.findViewById(R.id.menu2_text);
textView2.setText(Objects.requireNonNull(psyDetailInfo.get(2)).getTitle());
TextView textView3 = (TextView) linearLayout.findViewById(R.id.menu3_text);
textView3.setText(Objects.requireNonNull(psyDetailInfo.get(3)).getTitle());
TextView textView4 = (TextView) linearLayout.findViewById(R.id.menu4_text);
textView4.setText(Objects.requireNonNull(psyDetailInfo.get(4)).getTitle());
TextView textView = (TextView) linearLayout.findViewById(R.id.new_view_text);
textView.setText(result);
final RecyclerView recyclerView = (RecyclerView) linearLayout.findViewById(R.id.showCommentView);
final NewsItemAdapter adapter = new NewsItemAdapter(comment);
recyclerView.setAdapter(adapter);
adapter.setOnItemClickListener(new NewsItemAdapter.OnItemClickListener() {
@Override
public void onItemClick(final int postion, final View view) {
if (comment.get(postion).isZan()) {
final AlertDialog.Builder builder = new AlertDialog.Builder(curContext);
// builder.setTitle("确认");
builder.setMessage("是否取消点赞");
builder.setPositiveButton("是的", new OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, int which) {
int zanId = comment.get(postion).getId();
SubscribeUtils.subscribe(RetrofitManager.getInstance().getService(IMApiService.class).psyNoDizan(KeyContacts.token, zanId),
BaseBean.class, new NetListeren<BaseBean>() {
@Override
public void onSuccess(BaseBean baseBean) {
ToastUtil.showCustomToast("取消成功");
dialog.dismiss();
comment.get(postion).setZan(false);
comment.get(postion).setDizan(comment.get(postion).getDizan() - 1);
recyclerView.setAdapter(adapter);
}
});
}
});
builder.show();
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(curContext);
// builder.setTitle("确认");
builder.setMessage("是否进行点赞");
builder.setPositiveButton("是的", new OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, int which) {
int zanId = comment.get(postion).getId();
SubscribeUtils.subscribe(RetrofitManager.getInstance().getService(IMApiService.class).psyDizan(KeyContacts.token, zanId),
BaseBean.class, new NetListeren<BaseBean>() {
@Override
public void onSuccess(BaseBean baseBean) {
ToastUtil.showCustomToast("点赞成功");
dialog.dismiss();
comment.get(postion).setZan(true);
comment.get(postion).setDizan(comment.get(postion).getDizan() + 1);
recyclerView.setAdapter(adapter);
}
});
}
});
builder.show();
}
}
});
LinearLayout solve_ly = linearLayout.findViewById(R.id.solve_btn);
solve_ly.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if(tem_info[1]==0){
//说明该问题用户没有标记为已解决
AlertDialog.Builder builder = new AlertDialog.Builder(curContext);
builder.setMessage("该问题的解决思路以及方法是否有效?");
builder.setPositiveButton("是", new OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, int which) {
SubscribeUtils.subscribe(RetrofitManager.getInstance().getService(IMApiService.class).updatePosTeacherPsyMethodInfo(KeyContacts.token,
psyDetailInfo.get(4).getId(), tem_info[0], 1, Constants.CUR_CHAT_ID), BaseBean.class, new NetListeren<BaseBean>() {
@Override
public void onSuccess(BaseBean baseBean) {
tem_info[1] = 1;
ToastUtil.showCustomToast("感谢您的反馈");
dialog.dismiss();
}
});
}
});
builder.setNegativeButton("取消", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.show();
}
else if(tem_info[1]==1){
//说明该问题用户为已解决
AlertDialog.Builder builder = new AlertDialog.Builder(curContext);
builder.setMessage("是否取消有效?");
builder.setPositiveButton("是", new OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, int which) {
SubscribeUtils.subscribe(RetrofitManager.getInstance().getService(IMApiService.class).updatePosTeacherPsyMethodInfo(KeyContacts.token,
psyDetailInfo.get(4).getId(), tem_info[0], 0, Constants.CUR_CHAT_ID), BaseBean.class, new NetListeren<BaseBean>() {
@Override
public void onSuccess(BaseBean baseBean) {
tem_info[1] = 0;
ToastUtil.showCustomToast("感谢您的反馈");
dialog.dismiss();
}
});
}
});
builder.setNegativeButton("取消", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.show();
}
return false;
}
});
LinearLayout mark_ly = linearLayout.findViewById(R.id.mark_btn);
mark_ly.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if(tem_info[0]==0){
//说明该问题用户没有标记
AlertDialog.Builder builder = new AlertDialog.Builder(curContext);
builder.setMessage("是否标记该学生为此问题典型?");
builder.setPositiveButton("是", new OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, int which) {
SubscribeUtils.subscribe(RetrofitManager.getInstance().getService(IMApiService.class).updatePosTeacherPsyMethodInfo(KeyContacts.token,
psyDetailInfo.get(4).getId(), 1, tem_info[1], Constants.CUR_CHAT_ID), BaseBean.class, new NetListeren<BaseBean>() {
@Override
public void onSuccess(BaseBean baseBean) {
tem_info[0] = 1;
ToastUtil.showCustomToast("感谢您的反馈");
dialog.dismiss();
}
});
}
});
builder.setNegativeButton("否", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.show();
}else if(tem_info[0]==1){
//说明该问题用户有标记
AlertDialog.Builder builder = new AlertDialog.Builder(curContext);
builder.setMessage("是否取消标记该学生为此问题典型?");
builder.setPositiveButton("是", new OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, int which) {
SubscribeUtils.subscribe(RetrofitManager.getInstance().getService(IMApiService.class).updatePosTeacherPsyMethodInfo(KeyContacts.token,
psyDetailInfo.get(4).getId(), 0, tem_info[1], Constants.CUR_CHAT_ID), BaseBean.class, new NetListeren<BaseBean>() {
@Override
public void onSuccess(BaseBean baseBean) {
tem_info[0] = 0;
ToastUtil.showCustomToast("感谢您的反馈");
dialog.dismiss();
}
});
}
});
builder.setNegativeButton("否", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.show();
}
return false;
}
});
LinearLayout add_ly = linearLayout.findViewById(R.id.add_btn);
final ViewGroup tem = (ViewGroup) curView;
add_ly.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
//进入编辑的view
curMenu = 6;
views.get(views.size() - 1).setVisibility(View.GONE);
// if (views.contains(edit_view)) {
// int index = views.indexOf(views.contains(edit_view));
// views.get(index).setVisibility(View.VISIBLE);
// return false;
// }
View inflate = customViewMgr.getInflater().inflate(R.layout.add_opinion, tem, false);
LinearLayout linearLayout = (LinearLayout) inflate.findViewById(R.id.add_view);
ViewGroup parent1 = (ViewGroup) linearLayout.getParent();
parent1.removeView(linearLayout);
Button back_add_view = (Button) linearLayout.findViewById(R.id.back_add_view);
TextView hintText = (TextView) linearLayout.findViewById(R.id.opinion_hint);
final EditText editText = (EditText) linearLayout.findViewById(R.id.add_opinion_text);
hintText.setText(Objects.requireNonNull(psyDetailInfo.get(1)).getTitle()
+ "\n" + Objects.requireNonNull(psyDetailInfo.get(2)).getTitle()
+ "\n" + Objects.requireNonNull(psyDetailInfo.get(3)).getTitle()
+ "\n" + Objects.requireNonNull(psyDetailInfo.get(4)).getTitle());
back_add_view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
Button upload_opinion = (Button) linearLayout.findViewById(R.id.upload_new_opinion);
upload_opinion.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(curContext);
builder.setTitle("是否上传");
builder.setPositiveButton("确认", new OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, int which) {
SubscribeUtils.subscribe(RetrofitManager.getInstance().getService(IMApiService.class).uploadTeacherOpinion(KeyContacts.token,
psyDetailInfo.get(4).getId(), editText.getText().toString()), BaseBean.class, new NetListeren<BaseBean>() {
@Override
public void onSuccess(BaseBean baseBean) {
ToastUtil.showCustomToast("上传成功,请返回上一级再次进入");
dialog.dismiss();
onBackPressed();
}
});
}
});
builder.setNegativeButton("取消", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.show();
}
});
tem.addView(linearLayout);
views.add(linearLayout);
// setContentView(linearLayout);
return false;
}
});
tem.addView(linearLayout);
views.add(linearLayout);
}
});
}
}
后端开发
表设计
用了一个DbSchema
的工具,进行了表设计
子目录绑定父目录的id作为关联key
写之前觉得挺麻烦的, 实际做感觉也没那么复杂
DbSchema
这个工具有几个挺好用的功能,第一个是可以在这个设计好的表里面随机生成假数据,另一个是可以像msyql一样导出sql文件,这样就可以直接导入mysql了
书写接口
目前项目中创建了5个接口来实现
一个是查询各级目录信息的接口,通过请求的目录层级和上一级的pid进行查询
一个是查询详情页的信息的接口,包括详情页所属的前面所有父级目录的信息,以及评论区的信息,以及该详情页是否被用户进行过标记,并且上传过自己的想法的信息
一个是进行点赞的接口
一个是进行取消点赞的接口
还有一个是更新详情页状态的接口