從Android端到服務端全端開發------二級評論表的實現
前言:對於專門開發android端或者服務端某一端的開發者來說,對另一方可能也不太熟悉,希望通過這篇文章使大家更加熟悉另外一端,讓開發協作變得更加默契。
web伺服器端和app伺服器端的區別:
幾乎一樣,不過作為app,以下幾點是需要考慮的:
1、使用者的手機流量,由於手機套餐流量是一定的,不可能讓使用者每次都開啟原圖瀏覽,要根據使用者需要再決定原圖還是壓縮圖,還有就是要根據手機的尺寸去裁剪圖片的尺寸。
2、使用者的手機電量,因為每一次的請求資料都會消耗不少的電量,所以儘量要減少應用的資料請求次數。
3、資料的丟失,因為使用者手機上的訊號是根據所處的環境變化而變化,當訊號弱的時候,資料請求會有丟失的情況,載入不全。
用到的技術:
Android端:
app主體架構:ViewPager+Tablayout+Fragment,RecycleView巢狀RecycleView,ListView
網路請求:OkHttp
解析json:Gson
伺服器端:
Spring MVC+Spring+Mybatis+Spring Data JPA+SpringBoot2.0
二級評論表的資料庫設計可參照簡書作者:傑哥長得帥
傳送門:https://www.jianshu.com/p/5b757583eca7
模仿軟體:最右APP(最右的忠實粉絲)
最終效果:(介面完全沒優化,頭像等個人資訊也沒顯示出來,但是資料已經有了)
資料庫設計
invitation表(帖子表)
comment表(評論表)
reply表(回覆表)
user表(使用者表)
伺服器端:
專案結構:
首先使用IntlliJ Idea的Spring Initializr器建立專案,並新增mybatis、spring data jpa和web相應的starter依賴
config:配置類,相當於配置檔案xml
controller:控制層
entity和pojo:均為模型類,pojo作mybatis的模型,entity作jpa的模型
mapper和repository:均為dao層,mapper作mybatis的dao,repository作的jpa的dao
service:服務層
utils:工具類
application.properties:配置檔案
pojo
InvitationCustomer
public class InvitationCustomer {
//主鍵,id不要導錯包
private Integer id;
//帖子內容
private String invContent;
//帖子贊數
private Integer invLaud;
//一個帖子有多個評論
private List<CommentCustomer> comments;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getInvContent() {
return invContent;
}
public void setInvContent(String invContent) {
this.invContent = invContent;
}
public Integer getInvLaud() {
return invLaud;
}
public void setInvLaud(Integer invLaud) {
this.invLaud = invLaud;
}
public List<CommentCustomer> getComments() {
return comments;
}
public void setComments(List<CommentCustomer> comments) {
this.comments = comments;
}
@Override
public String toString() {
return "InvitationCustomer{" +
"id=" + id +
", invContent='" + invContent + '\'' +
", invLaud=" + invLaud +
", comments=" + comments +
'}';
}
}
CommentCustomer
public class CommentCustomer {
//主鍵
private Integer id;
//評論時間
private Date comDate;
//評論內容
private String comContent;
//評論贊數
private Integer comLaud;
//帖子的外來鍵
private Integer invId;
//評論使用者的外來鍵
private Integer userId;
//評論使用者
private UserCustomer user;
//一個評論有多個巢狀評論
private List<ReplyCustomer> replys;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Date getComDate() {
return comDate;
}
public void setComDate(Date comDate) {
this.comDate = comDate;
}
public String getComContent() {
return comContent;
}
public void setComContent(String comContent) {
this.comContent = comContent;
}
public Integer getComLaud() {
return comLaud;
}
public void setComLaud(Integer comLaud) {
this.comLaud = comLaud;
}
public UserCustomer getUser() {
return user;
}
public void setUser(UserCustomer user) {
this.user = user;
}
public Integer getInvId() {
return invId;
}
public void setInvId(Integer invId) {
this.invId = invId;
}
public List<ReplyCustomer> getReplys() {
return replys;
}
public void setReplys(List<ReplyCustomer> replys) {
this.replys = replys;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
@Override
public String toString() {
return "CommentCustomer{" +
"id=" + id +
", comDate=" + comDate +
", comContent='" + comContent + '\'' +
", comLaud=" + comLaud +
", invId=" + invId +
", userId=" + userId +
", user=" + user +
", replys=" + replys +
'}';
}
}
ReplyCustomer
public class ReplyCustomer {
//主鍵
private Integer id;
//回覆型別
private Integer reType;
//回覆內容
private String reContent;
//回覆的贊數
private Integer reLaud;
//回覆的帖子外來鍵
private Integer comId;
//回覆人的外來鍵
private Integer userIdFrom;
//被回覆人的外來鍵
private Integer userIdTo;
//回覆人
private UserCustomer userFrom;
//被回覆人
private UserCustomer userTo;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getReType() {
return reType;
}
public void setReType(Integer reType) {
this.reType = reType;
}
public String getReContent() {
return reContent;
}
public void setReContent(String reContent) {
this.reContent = reContent;
}
public Integer getReLaud() {
return reLaud;
}
public void setReLaud(Integer reLaud) {
this.reLaud = reLaud;
}
public Integer getComId() {
return comId;
}
public void setComId(Integer comId) {
this.comId = comId;
}
public UserCustomer getUserFrom() {
return userFrom;
}
public void setUserFrom(UserCustomer userFrom) {
this.userFrom = userFrom;
}
public UserCustomer getUserTo() {
return userTo;
}
public void setUserTo(UserCustomer userTo) {
this.userTo = userTo;
}
public Integer getUserIdFrom() {
return userIdFrom;
}
public void setUserIdFrom(Integer userIdFrom) {
this.userIdFrom = userIdFrom;
}
public Integer getUserIdTo() {
return userIdTo;
}
public void setUserIdTo(Integer userIdTo) {
this.userIdTo = userIdTo;
}
@Override
public String toString() {
return "ReplyCustomer{" +
"id=" + id +
", reType=" + reType +
", reContent='" + reContent + '\'' +
", reLaud=" + reLaud +
", comId=" + comId +
", userIdFrom=" + userIdFrom +
", userIdTo=" + userIdTo +
", userFrom=" + userFrom +
", userTo=" + userTo +
'}';
}
}
UserCustomer
public class UserCustomer {
//主鍵id
private Integer id;
//登入名
private String userLoginname;
//登入密碼
private String userPassword;
//姓名
private String userName;
//學號
private String userStudentID;
//頭像
private String userImage;
//微訊號
private String userWxNumber;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserLoginname() {
return userLoginname;
}
public void setUserLoginname(String userLoginname) {
this.userLoginname = userLoginname;
}
public String getUserPassword() {
return userPassword;
}
public void setUserPassword(String userPassword) {
this.userPassword = userPassword;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserStudentID() {
return userStudentID;
}
public void setUserStudentID(String userStudentID) {
this.userStudentID = userStudentID;
}
public String getUserImage() {
return userImage;
}
public void setUserImage(String userImage) {
this.userImage = userImage;
}
public String getUserWxNumber() {
return userWxNumber;
}
public void setUserWxNumber(String userWxNumber) {
this.userWxNumber = userWxNumber;
}
@Override
public String toString() {
return "UserCustomer{" +
"id=" + id +
", userLoginname='" + userLoginname + '\'' +
", userPassword='" + userPassword + '\'' +
", userName='" + userName + '\'' +
", userStudentID='" + userStudentID + '\'' +
", userImage='" + userImage + '\'' +
", userWxNumber='" + userWxNumber + '\'' +
'}';
}
}
entity
Invitation
@Entity
@Table(name = "invitation")
public class Invitation {
//主鍵,id不要導錯包
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
//帖子內容
@Column(name = "inv_content")
private String invContent;
//帖子贊數
@Column(name = "inv_laud")
private Integer invLaud;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getInvContent() {
return invContent;
}
public void setInvContent(String invContent) {
this.invContent = invContent;
}
public Integer getInvLaud() {
return invLaud;
}
public void setInvLaud(Integer invLaud) {
this.invLaud = invLaud;
}
}
Controller層,@ResponseBody返回的json資料格式,是和客戶端互動的最重要一環
InvitationController
@Controller
public class InvitationController {
//依賴注入
@Autowired
private InvitationService invitationService;
//找到所有的樹洞
@ResponseBody//返回json格式,此處做了一個分頁的操作,startPage開始頁數,sizePage頁面資料
@RequestMapping(value = "/invs/{startPage}/{sizePage}",method = RequestMethod.GET)
public Page<Invitation> findAllInvitations(@PathVariable("startPage") int startPage,@PathVariable("sizePage") int sizePage){
return invitationService.findAllInvitations(startPage,sizePage);
}
//某條樹洞詳情
@ResponseBody
@RequestMapping(value = "/inv/{id}",method = RequestMethod.GET)
public List<CommentCustomer> findDetalInvitation(@PathVariable("id") int id){
return invitationService.findDetalInvitation(id);
}
}
InvitationService
public interface InvitationService {
//找到所有的帖子,分頁
public Page<Invitation> findAllInvitations(int startPage, int sizePage);
//進入帖子詳情
public List<CommentCustomer> findDetalInvitation(Integer id);
}
InvitationServiceImpl
@Service
public class InvitationServiceImpl implements InvitationService{
//注入兩個dao層物件
@Autowired
private InvitationRepository invitationRepository;
@Autowired
private InvitationCustomerMapper invitationCustomerMapper;
@Override
public Page<Invitation> findAllInvitations(int startPage,int sizePage) {
Sort sort=new Sort(Sort.Direction.DESC,"id");
Pageable pageable=new PageRequest(startPage,sizePage,sort);
Page<Invitation> page = invitationRepository.findAll(pageable);
return page;
}
@Override
public List<CommentCustomer> findDetalInvitation(Integer id) {
return invitationCustomerMapper.findInvDetailById(id);
}
}
InvitationRepository是採用jpa方式,實現介面就可以進行增刪改查,適合於單表操作
public interface InvitationRepository extends JpaRepository<Invitation,Integer>,JpaSpecificationExecutor<Invitation>{
}
InvitationCustomerMapper是採用mybatis的方式,更適合於多表複雜的資料訪問
public interface InvitationCustomerMapper {
//根據帖子id查詢所有評論以及每一條評論的使用者
public List<CommentCustomer> findInvDetailById(Integer id);
}
對應的InvitationCustomerMapper.xml檔案
多表關聯,存在一對多的關係,注意,千萬不要使用內連線!!!
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.zdxh.assistant.mapper.InvitationCustomerMapper">
<select id="findInvDetailById" resultMap="findInvDetail" parameterType="integer">
SELECT
comment.id cid,
comment.com_date,
comment.com_content,
comment.com_laud,
comment.user_id,
comment.inv_id,
user.id uid,
user.user_loginname,
user.user_image,
reply.id rid,
reply.re_type,
reply.re_content,
reply.re_laud,
reply.com_id,
reply.user_id_from,
reply.user_id_to,
user_from.id uid,
user_from.user_loginname,
user_from.user_image
FROM invitation
LEFT JOIN comment
ON invitation.id=comment.inv_id
LEFT JOIN user
ON comment.user_id=user.id
LEFT JOIN reply
ON `comment`.id=reply.com_id
LEFT JOIN user user_from
ON reply.user_id_from=user_from.id
WHERE invitation.id=#{id}
</select>
<resultMap id="findInvDetail" type="cn.zdxh.assistant.pojo.CommentCustomer">
<id property="id" column="cid"/>
<result property="comDate" column="com_date"/>
<result property="comContent" column="com_content"/>
<result property="comLaud" column="com_laud"/>
<result property="userId" column="user_id"/>
<result property="invId" column="inv_id"/>
<association property="user" javaType="cn.zdxh.assistant.pojo.UserCustomer">
<id property="id" column="uid"/>
<result property="userLoginname" column="user_loginname"/>
<result property="userImage" column="user_image"/>
</association>
<collection property="replys" ofType="cn.zdxh.assistant.pojo.ReplyCustomer">
<id property="id" column="rid"/>
<result property="reType" column="re_type"/>
<result property="reContent" column="re_content"/>
<result property="reLaud" column="re_laud"/>
<result property="comId" column="com_id"/>
<result property="userIdFrom" column="user_id_from"/>
<result property="userIdTo" column="user_id_to"/>
<association property="userFrom" javaType="cn.zdxh.assistant.pojo.UserCustomer">
<id property="id" column="uid"/>
<result property="userLoginname" column="user_loginname"/>
<result property="userImage" column="user_image"/>
</association>
</collection>
</resultMap>
</mapper>
mybatis-config.xml mybatis的配置檔案,開啟駝峰命名法
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
</configuration>
使用mybatis的時候不要忘記開啟mapper掃描
啟動類中 SpringBootAssistantApplication
@MapperScan("cn.zdxh.assistant.mapper")//掃描mapper介面,相當於給每個類新增@Repository
@SpringBootApplication
public class SpringBootAssistantApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootAssistantApplication.class, args);
}
}
application.properties配置檔案
#配置mysql資料庫
spring.datasource.url= jdbc:mysql://localhost:3306/zdxh_assistant
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
#配置spring data jpa,已經預設開啟駝峰命名法
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
#配置mybatis的配置檔案以及對映檔案
mybatis.config-location=classpath:/mybatis/mybatis-config.xml
mybatis.mapper-locations=classpath:/mybatis/mapper/*.xml
#修改訪問埠
server.port=8090
Android端 :
專案結構
activity:放置activity檔案
adapter:放置介面卡adapter檔案
base:放置一些baseActivity或者baseFragment檔案
bean:放置模型類,這裡的模型類一定要實現Serializable介面,給頁面傳物件的時候Bundle需要序列化
fragment:放置fragment檔案
utils:工具類
layout:放置佈局檔案
主程式入口
MainActivity
public class MainActivity extends BaseActivity {
private ViewPager viewpager;
private TabLayout tableLayout;
private List<Fragment> mFragments;
private List<String> titleDatas;
private ImageView addView;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化控制元件
initView();
//初始化監聽器
initListener();
//初始化Tab
initTab();
//初始化Fragment
initFragment();
}
public void initView(){
viewpager=findViewById(R.id.vp_main);
tableLayout=findViewById(R.id.tl_main);
addView=findViewById(R.id.iv_add);
}
//右上角的加號控制元件
public void initListener(){
addView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 列表對話方塊
new AlertDialog.Builder(MainActivity.this)
.setTitle("選擇要釋出的資訊")
.setItems(new String[]{"釋出樹洞","釋出拼飯/傘"}, null)
.show();
}
});
}
//fragment中的tab要顯示的內容
public void initTab(){
titleDatas=new ArrayList<String>();
titleDatas.add("樹洞");
titleDatas.add("拼友");
titleDatas.add("我的");
}
//初始化Fragment
public void initFragment(){
mFragments = new ArrayList<Fragment>();
mFragments.add(new InvitationFragment());
mFragments.add(new PublishFragment());
mFragments.add(new MyFragment());
//初始化adapter
MainFragmentAdapter adapter = new MainFragmentAdapter(getSupportFragmentManager(), mFragments,titleDatas);
tableLayout.setupWithViewPager(viewpager);
// 將介面卡和ViewPager結合
viewpager.setAdapter(adapter);
}
}
activity_main.xml佈局檔案
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@color/colorPrimary">
<TextView
android:layout_width="100dp"
android:layout_height="30dp"
android:layout_marginLeft="20dp"
android:layout_centerVertical="true"
android:textSize="17sp"
android:textColor="#ffffff"
android:text="新華小助手"/>
<ImageView
android:id="@+id/iv_add"
android:layout_alignParentRight="true"
android:layout_marginRight="20dp"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_centerVertical="true"
android:background="@drawable/add"/>
</RelativeLayout>
<android.support.v4.view.ViewPager
android:id="@+id/vp_main"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="10">
</android.support.v4.view.ViewPager>
<android.support.design.widget.TabLayout
android:id="@+id/tl_main"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
</android.support.design.widget.TabLayout>
</LinearLayout>
MainFragmentAdapter介面卡
public class MainFragmentAdapter extends FragmentPagerAdapter {
private List<Fragment> mFragments;
private List<String> titleDatas;
//構造器,傳入必須的FragmentManager物件,以及Fragment和Tab
public MainFragmentAdapter(FragmentManager fm, List<Fragment> mFragments,List<String> titleDatas) {
super(fm);
this.mFragments=mFragments;
this.titleDatas=titleDatas;
}
@Override
public Fragment getItem(int position) {
return mFragments.get(position);
}
@Override
public int getCount() {
return mFragments.size();
}
@Override
public CharSequence getPageTitle(int position) {
return titleDatas.get(position);
}
}
fragment_invitation.xml檔案,三大fragment之一,樹洞的fragment佈局檔案
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".fragment.InvitationFragment">
<ListView
android:id="@+id/lv_invs"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
fragment_invitation_item.xml中listView中的item檔案
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_invs"
android:layout_width="match_parent"
android:layout_height="80dp"
android:textSize="20sp"
android:textColor="#000000"
android:text="新華小助手"/>
</LinearLayout>
fragment_my.xml 和fragment_publish.xml兩個檔案差不多,三大fragment之一
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragment.MyFragment">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="這是我的頁面"/>
</FrameLayout>
MyFragment和PublishFragment差不多,沒寫資料
public class MyFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_my, container, false);
}
}
InvitationFragment,資料寫在這個頁面
public class InvitationFragment extends Fragment {
private int INVITATIONS=1;
private List<Invitation> invitations;
private ListView listView;
@SuppressLint("HandlerLeak")
Handler handler=new Handler() {
@Override
public void handleMessage(Message message) {
super.handleMessage(message);
if (message.what == INVITATIONS){
//將請求所得資料傳進介面卡
InvitationsAdapter invitationsAdapter=new InvitationsAdapter(invitations,getContext());
listView.setAdapter(invitationsAdapter);
//初始化監聽器,傳入樹洞的id,然後在另外一個activity就根據這個id查詢樹洞詳情
initListener();
}
}
};
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//初始化控制元件
View view = inflater.inflate(R.layout.fragment_invitation, container, false);
listView=view.findViewById(R.id.lv_invs);
invitations=new ArrayList<>();
//請求獲取所有的樹洞,可以分頁
queryInvitations();
return view;
}
public void initListener(){
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent intent=new Intent(getActivity(), InvitationDetailActivity.class);
Bundle bundle=new Bundle();
Invitation invitationBudle = invitations.get(position);
bundle.putSerializable("invitation",invitationBudle);
intent.putExtras(bundle);
startActivity(intent);
}
});
}
public void queryInvitations() {
new Thread(new Runnable() {
@Override
public void run() {
//請求資料
String json = OkHttpUtils.OkHttpGet("http://47.107.126.98:8090/invs/0/20");
//Gson解析json資料
Gson gson = new Gson();
PageIns pageIns = gson.fromJson(json, PageIns.class);
invitations=pageIns.getContent();
//傳送訊息,不能在子執行緒更新UI
Message message = Message.obtain();
message.obj = invitations;
message.what = INVITATIONS;
handler.sendMessage(message);
}
}).start();
}
}
InvitationsAdapter介面卡
public class InvitationsAdapter extends BaseAdapter {
private List<Invitation> invitations;
private Context mContext;
public InvitationsAdapter(List<Invitation> invitations, Context mContext) {
this.invitations = invitations;
this.mContext=mContext;
}
@Override
public int getCount() {
return invitations.size();
}
@Override
public Object getItem(int position) {
return invitations.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view=null;
if (convertView==null){
//複用item物件
view= LayoutInflater.from(mContext).inflate(R.layout.fragment_invitation_item,parent,false);
}else {
view=convertView;
}
TextView textView=view.findViewById(R.id.tv_invs);
//ImageView imageView=view.findViewById(R.id.iv_invs);
textView.setText(invitations.get(position).getInvContent());
return view;
}
}
OkHttpUtils工具類,用來url請求 ,這裡是同步的請求,還有一種非同步的請求
public class OkHttpUtils {
public static String OkHttpGet(String url){
OkHttpClient okHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
.url(url)
.get()//get請求
.build();
final Call call = okHttpClient.newCall(request);
String json="";
try {
Response response = call.execute();
json=response.body().string();
} catch (IOException e) {
e.printStackTrace();
}
return json;
}
}
PageIns這個是用來介紹分頁後Invitation的
public class PageIns implements Serializable {
private List<Invitation> content;
public List<Invitation> getContent() {
return content;
}
public void setContent(List<Invitation> content) {
this.content = content;
}
}
Comment、Invitation、Reply、User和上面的CommentCustomer、InvitationCustomer、ReplyCustomer、UserCustomer內容都一樣的,只不過新增實現了介面Serializable介面
InvitationDetailActivity是點選樹洞進去之後的activity,樹洞詳情
public class InvitationDetailActivity extends BaseActivity {
private static int COMMENTS=1;
private List<Comment> comments;
private Invitation invitation;
private TextView invContent;
private RecyclerView recyclerView;
@SuppressLint("HandlerLeak")
Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//如果屬於資料請求的訊息,更新UI
if (msg.what==COMMENTS){
LinearLayoutManager layoutManager = new LinearLayoutManager(getApplicationContext());
recyclerView.setLayoutManager(layoutManager);
//設定監聽器
InvitationDetailAdapter adapter = new InvitationDetailAdapter((List<Comment>)msg.obj);
adapter.setItemClickListener(new InvitationDetailAdapter.OnItemClickListener() {
@Override
public void onItemClick(int position) {
// Toast.makeText(getApplicationContext(),"您點選了"+position+"行", Toast.LENGTH_SHORT).show();
Intent intent=new Intent(getApplicationContext(),CommentDetailActivity.class);
Bundle bundle=new Bundle();
//包裝物件傳給第二個Activity
Comment commentBundle = comments.get(position);
bundle.putSerializable("comment",commentBundle);
intent.putExtras(bundle);
startActivity(intent);
}
});
recyclerView.setAdapter(adapter);
}
}
};
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_invitation_detail);
initView();
initData();
}
public void initView(){
invContent=findViewById(R.id.tv_inv);
recyclerView=findViewById(R.id.rv_inv);
}
public void initData(){
//獲取上一個頁面fragment傳來的物件
Intent intent=getIntent();
Bundle bundle = intent.getExtras();
invitation=(Invitation) bundle.getSerializable("invitation");
//有資料後更新UI
invContent.setText(invitation.getInvContent());
int invId = invitation.getId();
comments=new ArrayList<Comment>();
//請求樹洞詳情
queryInvitationDetail(invId);
}
public void queryInvitationDetail(final int iId){
new Thread(new Runnable() {
@Override public void run() {
//請求資料
String json= OkHttpUtils.OkHttpGet("http://47.107.126.98:8090/inv/"+iId);
//Gson解析json資料
Gson gson=new Gson();
comments = gson.fromJson(json, new TypeToken<List<Comment>>(){}.getType());
//傳送訊息,不能在子執行緒更新UI
Message message=Message.obtain();
message.obj=comments;
message.what=COMMENTS;
handler.sendMessage(message);
}
}).start();//不要忘記start執行緒
}
}
樹洞詳情的recycleView介面卡的InvitationDetailAdapter
public class InvitationDetailAdapter extends RecyclerView.Adapter<InvitationDetailAdapter.ViewHold>{
private List<Reply> replies;
private List<Comment> comments;
private int position;
//點選事件的介面
private OnItemClickListener mItemClickListener;
/**
* 由於recycleView沒有監聽器,需要自定義監聽器介面
*/
public interface OnItemClickListener{
void onItemClick(int position);
}
public void setItemClickListener(OnItemClickListener itemClickListener) {
mItemClickListener = itemClickListener;
}
//建構函式
public InvitationDetailAdapter(List<Comment> comments) {
this.comments = comments;
}
public class ViewHold extends RecyclerView.ViewHolder{
private RecyclerView recyclerView;
private TextView textView;
public ViewHold(View itemView) {
super(itemView);
textView=(TextView)itemView.findViewById(R.id.tv_inv_info);
//巢狀RecycleView使用
recyclerView=itemView.findViewById(R.id.rv_inv_nest);
LinearLayoutManager layoutManager = new LinearLayoutManager(itemView.getContext());
layoutManager.setAutoMeasureEnabled(true);
recyclerView.setLayoutManager(layoutManager);
//方法載入順序的問題
if (replies==null){
//第一次載入的時候
replies=comments.get(position).getReplys();
} else {
//第二次往後的載入
replies=comments.get(position+1).getReplys();
}
//設定第二個recycleView的是配置
NestInvitationDetailAdapter adapter = new NestInvitationDetailAdapter(replies);
recyclerView.setAdapter(adapter);
}
}
@Override
public ViewHold onCreateViewHolder(ViewGroup parent, int viewType) {
View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.invitation_layout,parent,false);
ViewHold viewHold = new ViewHold(view);
if( mItemClickListener!= null){
view.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(View v) {
//獲取傳值過來的position
mItemClickListener.onItemClick((int)v.getTag());
}
});
}
return viewHold;
}
@Override
public void onBindViewHolder(ViewHold holder, int position) {
Comment comment = comments.get(position);
this.position=position;
// Log.e("哈哈哈哈哈哈哈",position+"");//從0開始逐漸遞增
holder.textView.setText(comment.getComContent());
//把item的position傳給onItemClick
holder.itemView.setTag(position);
}
@Override
public int getItemCount() {
return comments.size();
}
}
巢狀的Recycleview的介面卡
NestInvitationDetailAdapter
public class NestInvitationDetailAdapter extends RecyclerView.Adapter<NestInvitationDetailAdapter.ViewHold> {
private List<Reply> replies;
public NestInvitationDetailAdapter(List<Reply> replies) {
this.replies = replies;
}
public static class ViewHold extends RecyclerView.ViewHolder{
private TextView replyContent;
private TextView replyContent2;
public ViewHold(View itemView) {
super(itemView);
replyContent=(TextView)itemView.findViewById(R.id.tv_inv_info_nest);
replyContent2=(TextView)itemView.findViewById(R.id.tv_inv_info_nest2);
}
}
@Override
public ViewHold onCreateViewHolder(ViewGroup parent, int viewType) {
View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.invitation_layout_nest,parent,false);
ViewHold viewHold = new ViewHold(view);
return viewHold;
}
@Override
public void onBindViewHolder(ViewHold holder, int position) {
//Log.e("呵呵呵呵呵呵呵",position+"");
if (position==0){
//代表下拉
if (replies.size()>=2){
//回覆多於兩條的時候
holder.replyContent.setText(replies.get(position).getReContent());
holder.replyContent2.setText(replies.get(position+1).getReContent());
}else if(replies.size()==1){
//只有一條回覆
holder.replyContent.setText(replies.get(position).getReContent());
}
}else if (position==1){
//代表上劃
if (replies.size()>2){
holder.replyContent.setText(replies.get(position-1).getReContent());
holder.replyContent2.setText(replies.get(position).getReContent());
}else if(replies.size()==1){
holder.replyContent.setText(replies.get(position-1).getReContent());
}
}
}
@Override
public int getItemCount() {
return replies.size();
}
}
第一個recycleView的頁面,activity_invitation_detail.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".fragment.InvitationFragment">
<!-- TODO: Update blank fragment layout -->
<TextView
android:id="@+id/tv_inv"
android:layout_width="match_parent"
android:layout_height="100dp"
android:textSize="18sp"
android:textColor="#000000"
android:text="這是評論詳情頁面" />
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_inv"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
巢狀的recycleView頁面 invitation_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="100dp"
android:orientation="vertical">
<TextView
android:id="@+id/tv_inv_info"
android:layout_width="match_parent"
android:layout_height="50dp" />
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_inv_nest"
android:layout_width="match_parent"
android:layout_marginLeft="30dp"
android:layout_height="50dp"
android:background="#b1b1b1"/>
</LinearLayout>
對應的item檔案
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="vertical">
<TextView
android:id="@+id/tv_inv_info_nest"
android:layout_width="match_parent"
android:layout_height="25dp" />
<TextView
android:id="@+id/tv_inv_info_nest2"
android:layout_width="match_parent"
android:layout_height="25dp" />
</LinearLayout>
評論詳情CommentDetailActivity
public class CommentDetailActivity extends BaseActivity {
private Comment comment;
private ListView listView;
private TextView comContent;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_comment_detail);
initView();
initData();
}
public void initData(){
Intent intent = getIntent();
Bundle bundle = intent.getExtras();
//獲取上一個activity過來的物件
comment=(Comment) bundle.getSerializable("comment");
//部分值直接更新UI
comContent.setText(comment.getComContent());
//部分值傳到listView上
CommentDetailAdapter commentDetailAdapter=new CommentDetailAdapter(comment.getReplys(),getApplicationContext());
listView.setAdapter(commentDetailAdapter);
}
public void initView() {
comContent = findViewById(R.id.tv_com);
listView = findViewById(R.id.ll_com);
}
}
對應的listView介面卡CommentDetailAdapter
public class CommentDetailAdapter extends BaseAdapter {
private List<Reply> replies;
private Context mContext;
public CommentDetailAdapter(List<Reply> replies, Context mContext) {
this.replies = replies;
this.mContext=mContext;
}
@Override
public int getCount() {
return replies.size();
}
@Override
public Object getItem(int position) {
return replies.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view=null;
if (convertView==null){
//複用view,不會一直建立item,保持高效能
view= LayoutInflater.from(mContext).inflate(R.layout.activity_comment_detail_item,parent,false);
}else {
view=convertView;
}
TextView textView=view.findViewById(R.id.tv_com_item);
textView.setText(replies.get(position).getReContent());
return view;
}
}
activity_comment_detail.xml評論詳情佈局檔案
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".activity.CommentDetailActivity">
<TextView
android:id="@+id/tv_com"
android:layout_width="match_parent"
android:layout_height="100dp"
android:textSize="18sp"
android:textColor="#000000" />
<ListView
android:id="@+id/ll_com"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
activity_comment_detail_item.xml 評論詳情listView的item佈局檔案
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_com_item"
android:layout_width="match_parent"
android:layout_height="50dp"
android:textSize="20sp"
android:textColor="#000000"
android:text="評論回覆"/>
</LinearLayout>
總結:
互動最重點:
傳送json--->@ResponseBody
解析json---->Gson gson=new Gson();
List<Commennt> comments = gson.fromJson(json, new TypeToken<List<Comment>>(){}.getType());