基于云计算感应器的智能盆栽监测系统

一、立项目的、意义及特色

      目的:现代都市人群追求绿色、环保、健康的生活方式,人们常会种植一些盆栽来陶冶情操、舒缓紧张工作的心情。但是,忙碌的工作时常会让他们无暇顾及自己心爱的盆栽植物,或经常出差无法浇水导致盆栽植物缺水而干枯。研究表明,花草死亡是由于浇水不及时或长期曝晒等原因引起的。因此,能为人们打理盆栽植物的智能监测系统会成为都市人群向往的家居用品。

  本项目目的:智能盆栽监测系统由Raspberry Pi3、智能传感器、语音提示、LED显示、蓝牙、WiFi等模块同相应的APP软件进行交互,进行参数调整或者功能实现和社区论坛交流,并且能够独立完成对盆栽周围的温度,湿度和光照强度的有效监测。利用监测到的数据来有效调节利于盆栽的生长环境。

  意义:为解决用户对合理照顾盆栽感到困难的问题,本项目基于树莓派的智能盆栽通过传感器监测空气温湿度、土壤湿度、光照等条件,并根据监测到的数据来实现自动浇灌,提醒用户将盆栽移动至合适的位置。

二、智能盆栽监测系统总体设计

  研究主要内容:

  本项目的目的是利用ARM结构的嵌入式Raspberry Pi开发板搭建一个完整的嵌入式系统开发平台,并利用该平台建立基于mjpeg-streamer的嵌入式视频监控系统、烟雾传感器MQ-2、GY-30数字光强传感器、DHT11温湿度传感器、FC-28土壤湿度传感器、L298N电机驱动模块、LED发光二极管、蜂鸣器、步进电机。嵌入式应用系统的开发离不开嵌入式操作系统的支持,Raspberry Pi开发板搭载针对 Raspberry Pi 专门优化、基于 Debian 的 Raspbian OS,这款 OS 对浮点运算有更好的支持,能为用户带来更快的体验。另外在固件、核心、应用方面也都有了改进,而且据称它是最适合普通用户使用的 OS,同时设计一个符合本系统的文件系统和系统所需运用的各种类型设备的驱动程序。感知层监测温度、湿度、光照强度、烟雾、土壤湿度等环境参数,并根据监测到的数据进行响应(光照强度达到一定阈值自动启动照明装置;土壤湿度达到一定阈值自动启动浇灌装置;烟雾触发报警装置)。为了进一步实现嵌入式监测系统的功能,需要在嵌入式操作系统的基础之上,利用标准的TCP/IP协议设计相应的嵌入式Tornado WEB服务器和嵌入式流媒体服务器,并且集成了KNN Model算法以监测参数为依据对植物健康状况进行分析,最后设计移动终端监测软件,使其远程监测、远程控制、远程监控,并添加社区模块、机器人模块,可以同其他用户进行交流和学习更好地照料自己的植物。

1、系统硬件平台设计

  项目采用的是Raspberry Pi为主的嵌入式开发平台,开发板如图2.1所示:

                                                          图 2.1 Raspberry Pi开发平台

  Raspberry Pi是一款基于ARM的微型电脑主板,以SD/MicroSD卡为内存硬盘,卡片主板周围有1/2/4个USB接口和一个10/100 以太网接口(A型没有网口),可连接键盘、鼠标和网线,同时拥有视频模拟信号的电视输出接口和HDMI高清视频输出接口,以上部件全部整合在一张仅比信用卡稍大的主板上,具备所有PC的基本功能只需接通电视机和键盘,就能执行如电子表格、文字处理、播放高清视频等诸多功能。 Raspberry Pi B款只提供电脑板,无内存、电源、键盘、机箱或连线。

1)烟雾传感器MQ-2

  MQ-2烟雾传感器所使用的气敏材料是在清洁空气中电导率较低的二氧化锡(SnO2)。当传感器所处环境中存在可燃气体时,传感器的电导率随空气中可燃气体浓度的增加而增大。使用简单的电路即可将电导率的变化转换为与该气体浓度相对应的输出信号。

Welcome To My Blog:http://www.cnblogs.com/sirius-swu/p/6674428.html

2)GY-30数字光强传感器

  GY-30  采用  ROHM  原装  BH1750FVI  芯片供电电源:3-5v光照度范围:0-65535lx传感器内置16bitAD转换器直接数字输出,省略复杂的计算,省略标定不区分环  境光源接近于视觉灵敏度的分光特性可对广泛的亮度进行1勒克斯的高精度测定标准NXP IIC通信协议模块内部包含通信电平转换,与5v单片机io直接连接。

Welcome To My Blog:http://www.cnblogs.com/sirius-swu/p/6682746.html

3)DHT11温湿度传感器

  含有已校准数字信号输出的温湿度复合传感器。传感器包括电阻式感湿元件和一个NTC测温元件。

 

Welcome To My Blog:http://www.cnblogs.com/sirius-swu/p/6680952.html

4)FC-28土壤湿度传感器

  通过电位器调节控制相应阈值,湿度低于设定值时,DO输出高电平,高于设定值时,DO输出低电平;比较器采用LM393芯片,工作稳定。

 

Welcome To My Blog:http://www.cnblogs.com/sirius-swu/p/6674428.html

5)Raspberry Pi Camera v2

  Raspberry Pi Camera v2是树莓派新推出的官方摄像头板,采用高质量8百万像素索尼IMX219传感器扩展板,拥有定焦镜头,可以捕捉3280 x 2464像素静态图片和30FPS 1080P的视频,也支持1080p30, 720p60 and 640x480p60/90摄像功能。树莓派摄像头通过板上表面的小插槽连接树莓派,并使用专门为树莓派设计的CSI接口连接。

 

Welcome To My Blog:http://www.cnblogs.com/sirius-swu/p/6669093.html

6)L298N电机驱动模块

  内含两个H桥的高电压大电流全桥式驱动器,可以用来驱动直流电动机和步进电动机、继电器线圈等感性负载;采用标准逻辑电平信号控制;具有两个使能控制端,在不受输入信号影响的情况下允许或禁止器件工作有一个逻辑电源输入端,使内部逻辑电路部分在低电压下工作;可以外接检测电阻,将变化量反馈给控制电路。

7)LED发光二极管

  树莓派的IO口驱动能力是比较弱的,驱动电平为3.3V,高电平驱动比电平驱动能力稍弱些,但这也足够驱动led工作,为方便理解,以下实验以高电平驱动方式进行。IO口输出高电平,led灯亮,输出低电平则灭。

  连接电路:

 

  实物图如下:

  使用vi编辑器编写控制代码及编译运行:  

  1.登陆树莓派后,输入cd ./wiringPi 进入wiringPi目录,使用vi编辑器编辑c源文件,输入vim blink.c,如目录下有blink.c文件打开编辑,若无则会新建一个打开编辑。

cd ./wiringPi/
vim blink.c

   2.C语言代码,如下是使GPIO17间隔500ms交替输出高低电平的代码。

 

   对应各栏接口的标号,如以下程序使用的0即为树莓派的GPIO17接口也是物理接口的11接口。

#include <wiringPi.h>

int main (void){

    wiringPiSetup () ;
    pinMode (0, OUTPUT) ;
    for (;;){
        digitalWrite (0, HIGH) ; delay (500) ;
        digitalWrite (0, LOW) ; delay (500) ;
    }

    return 0 ;

}        

   3.输入gcc –o blink blink.c -lwiringPi编译、执行程序

gcc –o blink blink.c -lwiringPi
sudo ./blink

8)无源蜂鸣器

  内部没有震荡源,直流信号无法让它鸣叫。必须用去震荡的电流驱动它,2K-5KHZ的方波PWM (Pulse Width Modulation脉冲宽度调制)。 5KHZ的电流方波是啥意思?那就是每秒震动5K次,每一个完整的周期占用200us的时间,高点平占一部分时间,低电平占一部分时间。

 

  声音频率可控,可以做出不同的音效。

  有源蜂鸣器:内部带震荡电路,一通电就鸣叫,所以可以跟前面LED一样,给个高电平就能响,编程比无源的方便。

Welcome To My Blog:http://www.cnblogs.com/sirius-swu/p/6823882.html

9)步进电机

  步进电动机是一种将电脉冲信号转换成角位移或线位移的机电元件。步进电动机的输入量是脉冲序列,输出量则为相应的增量位移或步进运动。

Welcome To My Blog:http://www.cnblogs.com/sirius-swu/p/6668894.html

2、系统软件模块设计

1)发现

图片轮播:

xml布局文件

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="200dip"
    android:id="@+id/waterFlow">

    <android.support.v4.view.ViewPager
        android:id="@+id/vp"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="35dip"
        android:layout_gravity="bottom"
        android:background="#33000000"
        android:gravity="center"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="图片标题"
            android:textColor="@android:color/white" />

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="3dip"
            android:orientation="horizontal" >

            <View
                android:id="@+id/dot_0"
                android:layout_width="5dip"
                android:layout_height="5dip"
                android:layout_marginLeft="2dip"
                android:layout_marginRight="2dip"
                android:background="@drawable/dot_focused"/>

            <View
                android:id="@+id/dot_1"
                android:layout_width="5dip"
                android:layout_height="5dip"
                android:layout_marginLeft="2dip"
                android:layout_marginRight="2dip"
                android:background="@drawable/dot_normal"/>
            <View
                android:id="@+id/dot_2"
                android:layout_width="5dip"
                android:layout_height="5dip"
                android:layout_marginLeft="2dip"
                android:layout_marginRight="2dip"
                android:background="@drawable/dot_normal"/>
            <View
                android:id="@+id/dot_3"
                android:layout_width="5dip"
                android:layout_height="5dip"
                android:layout_marginLeft="2dip"
                android:layout_marginRight="2dip"
                android:background="@drawable/dot_normal"/>
            <View
                android:id="@+id/dot_4"
                android:layout_width="5dip"
                android:layout_height="5dip"
                android:layout_marginLeft="2dip"
                android:layout_marginRight="2dip"
                android:background="@drawable/dot_normal"/>

        </LinearLayout>
    </LinearLayout>
</FrameLayout>

 java文件

//显示的图片
images = new ArrayList<ImageView>();
for(int i = 0; i < imageIds.length; i++){
    ImageView imageView = new ImageView(this);
    imageView.setBackgroundResource(imageIds[i]);
    images.add(imageView);
}
//显示的小点
dots = new ArrayList<View>();
dots.add(findViewById(R.id.dot_0));
dots.add(findViewById(R.id.dot_1));
dots.add(findViewById(R.id.dot_2));
dots.add(findViewById(R.id.dot_3));
dots.add(findViewById(R.id.dot_4));

title = (TextView) findViewById(R.id.title);
title.setText(titles[0]);

adapter = new ViewPagerAdapter();
mViewPaper.setAdapter(adapter);

mViewPaper.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {


    @Override
    public void onPageSelected(int position) {
        title.setText(titles[position]);
        dots.get(position).setBackgroundResource(R.drawable.dot_focused);
        dots.get(oldPosition).setBackgroundResource(R.drawable.dot_normal);

        oldPosition = position;
        currentItem = position;
    }

    @Override
    public void onPageScrolled(int arg0, float arg1, int arg2) {

    }

    @Override
    public void onPageScrollStateChanged(int arg0) {

    }
});

 瀑布流展示(上拉加载、下拉刷新):

xml布局文件

<discover.widget.TryRefreshableView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/trymyRV"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:focusable="true"
    android:layout_marginTop="200dp"
    android:orientation="vertical" >

    <discover.widget.TryPullToRefreshScrollView
        android:id="@+id/waterfall_scroll"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:scrollbars="vertical" >

        <LinearLayout
            android:id="@+id/waterfall_container"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:background="@drawable/background" >
        </LinearLayout>
    </discover.widget.TryPullToRefreshScrollView>

</discover.widget.TryRefreshableView>

 java文件

// 瀑布流布局
all_screen_view = new ArrayList<LinkedList<View>>();

display = this.getWindowManager().getDefaultDisplay();
// 根据屏幕大小计算每列大小
item_width = display.getWidth() / column_count + 2;

column_height = new int[column_count];
context = this;
pin_mark = new HashMap[column_count];

fb = new FinalBitmap(this).init();// 必须调用init初始化FinalBitmap模块
fb.setCompleteListener(this);

this.lineIndex = new int[column_count];
this.bottomIndex = new int[column_count];
this.topIndex = new int[column_count];

for (int i = 0; i < column_count; i++) {
    lineIndex[i] = -1;
    bottomIndex[i] = -1;
    pin_mark[i] = new HashMap();
}

InitLayout();

// 瀑布流布局
private void InitLayout() {
waterfall_scroll = (TryPullToRefreshScrollView) findViewById(R.id.waterfall_scroll);
rv = (TryRefreshableView) findViewById(R.id.trymyRV);
rv.sv = waterfall_scroll;
// 隐藏mfooterView
rv.setRefreshListener(new TryRefreshableView.RefreshListener() {

    @Override
    public void onDownRefresh() {
        if (rv.mRefreshState == TryRefreshableView.READYTOREFRESH) {
            // 记录第一个view的位置
            firstView = waterfall_items.get(0).getChildAt(0);
            refreshType = DOWNREFRESH;
            AddItemToContainer(++current_page, page_count);
        }
    }
});
rv.setOnBottomListener(new TryRefreshableView.OnBottomListener() {

    @Override
    public void onBottom() {
        if (rv.mRefreshState != TryRefreshableView.REFRESHING) {
            refreshType = UPREFRESH;
            AddItemToContainer(++current_page, page_count);
        }
    }
});

waterfall_scroll.setOnScrollListener(new TryPullToRefreshScrollView.OnScrollListener() {
    @Override
    public void onAutoScroll(int l, int t, int oldl, int oldt) {

        // Log.d("MainActivity",
        // String.format("%d  %d  %d  %d", l, t, oldl, oldt));

        // Log.d("MainActivity", "range:" + range);
        // Log.d("MainActivity", "range-t:" + (range - t));

        if (pin_mark.length <= 0) {
            return;
        }
        scroll_height = waterfall_scroll.getMeasuredHeight();
        Log.d("MainActivity", "scroll_height:" + scroll_height);

        if (t > oldt) {// 向下滚动
            if (t > 3 * scroll_height) {// 超过两屏幕后

                for (int k = 0; k < column_count; k++) {

                    LinearLayout localLinearLayout = waterfall_items
                            .get(k);

                    if (pin_mark[k].get(Math.min(bottomIndex[k] + 1,
                            lineIndex[k])) <= t + 3 * scroll_height) {// 最底部的图片位置小于当前t+3*屏幕高度
                        View childAt = localLinearLayout
                                .getChildAt(Math.min(
                                        1 + bottomIndex[k],
                                        lineIndex[k]));
                        if(childAt != null){
                            FlowView picView = (FlowView) childAt
                                    .findViewById(R.id.news_pic);
                            if (picView.bitmap == null
                                    && !TextUtils.isEmpty(picView.get_url())) {
                                fb.reload(picView.get_url(), picView);
                            }

                            bottomIndex[k] = Math.min(1 + bottomIndex[k],
                                    lineIndex[k]);
                        }
                    }
                    // Log.d("MainActivity",
                    // "headIndex:" + topIndex[k] + "  footIndex:"
                    // + bottomIndex[k] + "  headHeight:"
                    // + pin_mark[k].get(topIndex[k]));
                    if (pin_mark[k].get(topIndex[k]) < t - 2
                            * scroll_height) {// 未回收图片的最高位置<t-两倍屏幕高度

                        int i1 = topIndex[k];
                        topIndex[k]++;
                        ((FlowView) localLinearLayout.getChildAt(i1)
                                .findViewById(R.id.news_pic)).recycle();
                        Log.d("MainActivity", "recycle,k:" + k
                                + " headindex:" + topIndex[k]);

                    }
                }

            }
        } else {// 向上滚动
            if (t > 3 * scroll_height) {// 超过两屏幕后
                for (int k = 0; k < column_count; k++) {
                    LinearLayout localLinearLayout = waterfall_items
                            .get(k);
                    if (pin_mark[k].get(bottomIndex[k]) > t + 3
                            * scroll_height) {
                        ((FlowView) localLinearLayout.getChildAt(
                                bottomIndex[k]).findViewById(
                                R.id.news_pic)).recycle();
                        Log.d("MainActivity", "recycle,k:" + k
                                + " headindex:" + topIndex[k]);

                        bottomIndex[k]--;
                    }

                    if (pin_mark[k].get(Math.max(topIndex[k] - 1, 0)) >= t
                            - 2 * scroll_height) {
                        FlowView picView = ((FlowView) localLinearLayout
                                .getChildAt(
                                        Math.max(-1 + topIndex[k], 0))
                                .findViewById(R.id.news_pic));

                        if (picView.bitmap == null
                                && !TextUtils.isEmpty(picView.get_url())) {
                            fb.reload(picView.get_url(), picView);
                        }

                        topIndex[k] = Math.max(topIndex[k] - 1, 0);
                    }
                }
            }

        }
    }
});

2)环境参数

 

3)机器人

图灵API:

xml布局文件

<ListView
    android:id="@+id/lv"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1"
    android:transcriptMode="alwaysScroll"
    android:divider="@null"
    android:textColor="#000000"
    android:listSelector="@android:color/transparent"></ListView>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <EditText
        android:id="@+id/sendText"
        android:layout_width="0dp"
        android:textColor="#000000"
        android:layout_height="wrap_content"
        android:layout_weight="1" />

    <Button
        android:id="@+id/send_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/send"/>
</LinearLayout>

 java文件

private void initView() {
    getActionBar().setTitle(string.robot);
    lv = (ListView) findViewById(id.lv);
    sendText = (EditText) findViewById(id.sendText);
    sendBtn = (Button) findViewById(id.send_btn);
    lists = new ArrayList<ListData>();
    sendBtn.setOnClickListener(this);
    adapter = new TextAdapter(lists, this);
    lv.setAdapter(adapter);
    ListData listData;
    listData = new ListData(getRandomWelcomeTips(), ListData.RECEIVER, getTime());
    lists.add(listData);
}

private String getRandomWelcomeTips() {
    String welcome_tip = null;
    welcome_array = this.getResources().getStringArray(R.array.welcome_tips);
    int index = (int) (Math.random()*(welcome_array.length-1));
    welcome_tip = welcome_array[index];
    return welcome_tip;
}

@Override
public void getDataUrl(String data) {
//        System.out.println(data);
    parseText(data);
}

public void parseText(String str) {
    try {
        JSONObject jb = new JSONObject(str);
//            System.out.println(jb.getString("code"));
//            System.out.println(jb.getString("text"));
        ListData listData;
        listData = new ListData(jb.getString("text"), ListData.RECEIVER, getTime());
        lists.add(listData);
        adapter.notifyDataSetChanged();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

@Override
public void onClick(View v) {
    getTime();
    content_str = sendText.getText().toString();
    sendText.setText("");
    String dropk = content_str.replace(" ", "");
    String droph = dropk.replace("
", "");
    ListData listData;
    listData =new ListData(content_str, ListData.SEND, getTime());
    lists.add(listData);
    if (lists.size()>30) {
        for (int i = 0; i < lists.size(); i++) {
            lists.remove(i);
        }
    }
    adapter.notifyDataSetChanged();
    httpData = (HttpData) new HttpData("http://www.tuling123.com/openapi/api?key=d20467b5e6c340c3a18a4557ebf31693&info="+droph, this).execute();
}

private String getTime() {
    currentTime = System.currentTimeMillis();
    SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");
    Date curDate = new Date();
    String str = format.format(curDate);
    if (currentTime-oldTime >= 5*60*1000) {
        oldTime = currentTime;
        return str;
    } else {
        return "";
    }
}

4)健康报告

5)终端控制

BUG解析及解决方案:

1.移动不断发出控制指令

setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的参数。

去掉setInterval() 方法不需要自动重复调用。

2.蜂鸣器打开、关闭操作之后再次打开响声不连续

执行开启程序文件关闭程序文件操作,由于关闭程序文件未关闭导致再次开启程序只发出短暂的响声就停止了。

mProcess = subprocess.Popen(shellLine)来创建进程,mProcess.kill()函数去结束该进程

6)视频监控

7)论坛

8)消息

9)盆友圈

xml布局文件:

<android.support.v7.widget.RecyclerView
        android:id="@+id/rv_text_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></android.support.v7.widget.RecyclerView>

 java 文件

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_pot);

    getActionBar().setTitle("盆友圈");

    mRvTextList= (RecyclerView) findViewById(R.id.rv_text_list);
    mRvTextList.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
    mRvTextList.setAdapter(new TextListAdapter(this));
}

 TextListAdapter.java文件

package pot;

/**
 * Created by sirius_swu on 2017/3/19.
 */
import android.app.Activity;
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.Adapter;
import android.widget.TextView;

import com.special.ResideMenuDemo.R;

public class TextListAdapter extends RecyclerView.Adapter<TextListAdapter.TextHolder> {
    private Activity mContent;

    private final int MAX_LINE_COUNT = 3;

    private final int STATE_UNKNOW = -1;

    private final int STATE_NOT_OVERFLOW = 1;//文本行数不能超过限定行数

    private final int STATE_COLLAPSED = 2;//文本行数超过限定行数,进行折叠

    private final int STATE_EXPANDED = 3;//文本超过限定行数,被点击全文展开

    private SparseArray<Integer> mTextStateList;

    public TextListAdapter(Activity context) {
        mContent = context;
        mTextStateList = new SparseArray<Integer>();
    }

    @Override
    public TextHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new TextHolder(mContent.getLayoutInflater().inflate(R.layout.item_test_list, parent, false));
    }

    @Override
    public void onBindViewHolder(final TextHolder holder,final int position) {
        holder.hend.setText(position+1+"");//设置头部的文字
        holder.name.setText(Util.getName(position));//设置名称
        int state=mTextStateList.get(position,STATE_UNKNOW);
//        如果该itme是第一次初始化,则取获取文本的行数
        if (state==STATE_UNKNOW){
            holder.content.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                @Override
                public boolean onPreDraw() {
//                    这个回掉会调用多次,获取玩行数后记得注销监听
                    holder.content.getViewTreeObserver().removeOnPreDrawListener(this);
//                    holder.content.getViewTreeObserver().addOnPreDrawListener(null);
//                    如果内容显示的行数大于限定显示行数
                    if (holder.content.getLineCount()>MAX_LINE_COUNT) {
                        holder.content.setMaxLines(MAX_LINE_COUNT);//设置最大显示行数
                        holder.expandOrCollapse.setVisibility(View.VISIBLE);//让其显示全文的文本框状态为显示
                        holder.expandOrCollapse.setText("全文");//设置其文字为全文
                        mTextStateList.put(position, STATE_COLLAPSED);
                    }else{
                        holder.expandOrCollapse.setVisibility(View.GONE);//显示全文隐藏
                        mTextStateList.put(position,STATE_NOT_OVERFLOW);//让其不能超过限定的行数
                    }
                    return true;
                }
            });

            holder.content.setMaxLines(Integer.MAX_VALUE);//设置文本的最大行数,为整数的最大数值
            holder.content.setText(Util.getContent(position));//用Util中的getContent方法获取内容
        }else{
//            如果之前已经初始化过了,则使用保存的状态,无需在获取一次
            switch (state){
                case STATE_NOT_OVERFLOW:
                    holder.expandOrCollapse.setVisibility(View.GONE);
                    break;
                case STATE_COLLAPSED:
                    holder.content.setMaxLines(MAX_LINE_COUNT);
                    holder.expandOrCollapse.setVisibility(View.VISIBLE);
                    holder.expandOrCollapse.setText("全文");
                    break;
                case STATE_EXPANDED:
                    holder.content.setMaxLines(Integer.MAX_VALUE);
                    holder.expandOrCollapse.setVisibility(View.VISIBLE);
                    holder.expandOrCollapse.setText("收起");
                    break;
            }
            holder.content.setText(Util.getContent(position));
        }


//        设置显示和收起的点击事件
        holder.expandOrCollapse.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int state=mTextStateList.get(position,STATE_UNKNOW);
                if (state==STATE_COLLAPSED){
                    holder.content.setMaxLines(Integer.MAX_VALUE);
                    holder.expandOrCollapse.setText("收起");
                    mTextStateList.put(position,STATE_EXPANDED);
                }else if (state==STATE_EXPANDED){
                    holder.content.setMaxLines(MAX_LINE_COUNT);
                    holder.expandOrCollapse.setText("全文");
                    mTextStateList.put(position,STATE_COLLAPSED);
                }
            }
        });

    }

    @Override
    public int getItemCount() {
        return 15;
    }

    public class TextHolder extends RecyclerView.ViewHolder {
        public TextView hend;
        public TextView name;
        public TextView content;
        public TextView expandOrCollapse;

        public TextHolder(View itemView) {
            super(itemView);
//            绑定xml布局中的控件
            hend = (TextView) itemView.findViewById(R.id.tv_hend);
            name = (TextView) itemView.findViewById(R.id.tv_name);
            content = (TextView) itemView.findViewById(R.id.tv_content);
            expandOrCollapse = (TextView) itemView.findViewById(R.id.tv_expand_or_collapse);
        }
    }
}

三、系统设计思维导图

原文地址:https://www.cnblogs.com/sirius-swu/p/6823634.html