1. 程式人生 > >從Android端到服務端全端開發------二級評論表的實現

從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());