Android:实现安卓小程序-记事本(备忘录)的开发

这篇博客详细记录了Android环境下记事本应用的开发过程,包括记事本的基本设计、数据存储、主界面设计、编辑功能、背景色设置、闹钟功能和高级搜索等。开发者采用文件存储而非SQLite,实现了浮动菜单、滑动删除、自定义Adapter等功能,并引入了动态搜索框第三方库。通过这个项目,作者提升了对Intent、Adapter等技术的掌握,也学习了程序UI美化和数据存储的新知识。

目录

1. 前言

2. 记事本功能需求

3. 部分关键代码解析及程序截图

3.1 记事本类的基本设计

3.2 记事本的数据存储设计

3.3 主界面的设计

3.4 记事本的编辑

3.5 记事本的背景色设置

3.6 记事本的闹铃功能

3.7 高级搜索

4. 总结


1. 前言

在学习安卓开发这门课程中期,学会了安卓开发的基本知识,比如,UI组件的应用,Intent的应用等等,同时期中作业为编写一个记事本程序,基于google的记事本demo, 但对于我本人来说,我更喜欢对代码从头到位都有自己的参与,于是我决定自己从头编写一个基本记事本的开发和记事本的相关扩展功能。

开发环境: Android Studio (API25以上)

2. 记事本功能需求

功能名称功能概述优先级
记事本基本功能对记事本的增删改最高
时间戳显示记事本的最近编辑时间的显示
高级搜索对记事本的查找
UI美化对用户界面的美化
记事本背景色的设置记事本背景色的设置
记事本闹钟设置记事本的闹钟功能设置

 

 

 

 

 

 

 

3. 部分关键代码解析及程序截图

3.1 记事本类的基本设计

Note.java

//序列化便于本地存储
public class Note  implements Serializable,Comparable{

    private String title;
    private String create_date;
    private String update_date;
    private String text;
    private String background;
    private String date_alarm;
    public Note(String title,String text) {
        //标题
        this.title = title;
        //创建日期
        this.create_date = new SimpleDateFormat("yyyy-MM-dd HH-mm").format(new Date());
        //更新日期
        this.update_date = this.create_date;
        //内容
        this.text = text;
        //背景色
        this.background = null;
        //闹钟日期
        this.date_alarm = "";
    }
}

3.2 记事本的数据存储设计

这里由于我当时暂未学习到SQLite的数据存储方式,采用了文件读写的方式来存储记事本的条目。

首先,在AndroidManifest.xml中设置允许程序读写文件的权限。

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.VIBRATE" />

将Note存储在哈希表内,再将哈希表的内容写入文件中,同时文件存储在内置的SD卡的路径下。

NoteManager.java

public class NoteManager {

    private Context mContext;
    private List<Note> list;
    private String root_file;
    public NoteManager(Context context){
        root_file = Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator+"NoteList";
        this.mContext = context;
        list = getNoteList();
    }
    
    //更新当前notes列表
    public void updateNoteList(List<Note> now_data){
        try {
            File file = new File(root_file);
            if (!file.exists()){
                file.createNewFile();
            }
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(root_file));
            oos.writeObject(now_data);
            oos.close();
        }
        catch (Exception e1){
            e1.printStackTrace();
        }
    }
}

3.3 主界面的设计

为了更好的用户体验,决定将用户设计设计成两种情况,一种为用户程序中暂无存储记事文件的情况下,一种为程序中已存有记事文件下。附上部分关键代码以及程序截图。

主界面-1主界面-2
//检查数据列表是否为空,如果为空,那么渲染blank_View
    public void emptyListCheck(){
        int number = 0;
        if(data!=null){
            number=data.size();
        }
        if(number == 0) {
            smlvMain.setVisibility(View.GONE);
            RelativeLayout empty = (RelativeLayout) findViewById(R.id.blank_view);
            empty.setVisibility(View.VISIBLE);
            fabMenu.setVisibility(View.VISIBLE);
        }else{
            smlvMain.setVisibility(View.VISIBLE);
            RelativeLayout empty = (RelativeLayout) findViewById(R.id.blank_view);
            empty.setVisibility(View.GONE);
        }
    }

这里设置了Note条目的菜单模块和空白模块,若数据列表为空,则隐藏条目菜单模块,显示空白模块,若不为空,则隐藏空白模块,显示菜单模块。

3.4 记事本的编辑

可以看到在主界面的右下角存在一个浮动窗口菜单,这里涉及到第三方依赖的添加:

com.getbase.floatingactionbutton.FloatingActionButton

res/layout/activity_main.xml

<com.getbase.floatingactionbutton.FloatingActionsMenu
            adroid:id="@+id/fab_menu"
            adroid:layout_width="wrap_content"
            adroid:layout_height="wrap_content"
            adroid:layout_alignParentRight="true"
            adroid:layout_alignParentBottom="true"
            fab:fab_labelStyle="@style/fab_label_style"
            app:fab_addButtonSize="mini"
            fab:fab_addButtonColorNormal="#5e96b9"
            fab:fab_addButtonColorPressed="@null"
            fab:fab_addButtonPlusIconColor="@color/black"
            adroid:layout_marginBottom="16dp"
            adroid:layout_marginRight="16dp">

            <com.getbase.floatingactionbutton.FloatingActionButton
                adroid:id="@+id/fab_add"
                adroid:layout_width="wrap_content"
                adroid:layout_height="wrap_content"
                fab:fab_colorNormal="@color/white"
                fab:fab_colorPressed="@color/green"
                fab:fab_title="新备忘录"
                fab:fab_size="mini"
                fab:fab_icon="@drawable/fab_add" />
            <com.getbase.floatingactionbutton.FloatingActionButton
                adroid:id="@+id/fab_exit"
                adroid:layout_width="wrap_content"
                adroid:layout_height="wrap_content"
                fab:fab_colorNormal="@color/white"
                fab:fab_colorPressed="@color/green"
                fab:fab_title="退出应用"
                fab:fab_size="mini"
                app:fab_icon="@drawable/fab_exit" />
</com.getbase.floatingactionbutton.FloatingActionsMenu>

这里可以看到对浮动菜单栏的xml设计,包含两种功能,新建Note和应用的退出,涉及到功能的逻辑代码这里就不赘述了。

新建Note后,可在主界面-2下的点击Note条目或左滑条目进入编辑模式。

编辑模式-1编辑模式-2

 

同时可以注意到两者编辑模式略有不同,在编辑模式-1下,可以做到修改,删除,背景色的设置,闹铃的添加,在编辑模式-2下仅仅能做到修改和删除。

左滑Note条目左滑部分关键代码:

首先需要新建一个滑动view类

public class MyScrollView extends ScrollView {
   private ScrollViewListener scrollViewListener = null;

    public MyScrollView(Context context) {
        super(context);
    }

    public MyScrollView(Context context, AttributeSet attrs,
                        int defStyle) {
        super(context, attrs, defStyle);
    }

    public MyScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void setOnScrollListener(ScrollViewListener scrollViewListener) {
        this.scrollViewListener = scrollViewListener;
    }

    @Override
    protected void onScrollChanged(int x, int y, int oldx, int oldy) {
        super.onScrollChanged(x, y, oldx, oldy);
        if (scrollViewListener != null) {
            if (oldy < y && ((y - oldy) > 15)) {
                scrollViewListener.onScroll(y - oldy);
            } else if (oldy > y && (oldy - y) > 15) {
                scrollViewListener.onScroll(y - oldy);
            }
        }
    }
//这一部分参考了github某个开源项目
    public  interface ScrollViewListener{//dy Y轴滑动距离
        void onScroll(int dy);
    }
}

设置完毕后在xml文件中即可设置应用:

<com.zc.memo.view.MyScrollView
            adroid:id="@+id/sv_main"
            adroid:layout_width="match_parent"
            adroid:layout_height="wrap_content"
            adroid:background="@drawable/border_bottom_null">
            <LinearLayout
                adroid:layout_width="match_parent"
                adroid:layout_height="wrap_content"
                adroid:orientation="vertical">
                <com.baoyz.swipemenulistview.SwipeMenuListView
                    adroid:id="@+id/smlv_main"
                    adroid:layout_width="match_parent"
                    adroid:layout_height="match_parent"
                    adroid:descendantFocusability="blocksDescendants"
                    adroid:divider="@color/little_gray"
                    adroid:dividerHeight="1dp"
                    adroid:background="@null"
                    />
            </LinearLayout>
</com.zc.memo.view.MyScrollView>

接着在activity中,初始化滑动窗口及滑动窗口菜单。

 private void initView() {

        data = new NoteManager(context).getNoteList();
        smlvMain.setAdapter(new NoteAdapter(context,data));
        smlvMain.setMenuCreator(new MySwipeMenuCreator(context));
        smlvMain.setSwipeDirection(SwipeMenuListView.DIRECTION_LEFT);
        new ListViewUtil().setListViewHeightBasedOnChildren(smlvMain);
        svMain.setOnScrollListener(new MyScrollView.ScrollViewListener() {
            @Override
            public void onScroll(int dy) {
                if (dy > 0) {
                    //下滑
                    showOrHideFab(false);
                } else if (dy <= 0 ) {
                    //上滑
                    showOrHideFab(true);
                }
            }
        });
        viewListener();
        emptyListCheck();
    }

这里仅放上如何初始化滑动窗口的view,关于监听这个滑动窗口的逻辑代码在这里就不赘述了。

接下来讲一讲自定义NoteAdapter适配器来展示即使条目,首先继承BaseAdapter实现一个抽象类MyBaseAdapter<T>该类可读取List列表的数据。

public abstract class MyBaseAdapter<T> extends BaseAdapter {
    Context context;
    List<T> data;

    public MyBaseAdapter(Context context, List<T> data) {
        this.context = context;
        this.data = data;
    }

    @Override
    public Object getItem(int i) {
        return null;
    }

    @Override
    public long getItemId(int i) {
        return 0;
    }
    @Override
    public int getCount() {
        if(data==null) return 0;
        return data.size();
    }
}

接着在编写NoteAdapter类继承上类,初始化view, 将Note条目的各项信息展示出来,其中还自定义了一个viewHolder类,方便对xml文件中的各项组件进行绑定编辑。

public class NoteAdapter extends MyBaseAdapter<Note> {

    public NoteAdapter(Context context, List<Note> data) {
        super(context, data);
    }
    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        ViewHolder viewHolder;
        if(view==null){
            viewHolder=new ViewHolder();
            LayoutInflater inflater = LayoutInflater.from(context);
            view = inflater.inflate(R.layout.item_main_list, viewGroup, false);
            viewHolder.tv_lv_month = (TextView) view.findViewById(R.id.tv_lv_month);
            viewHolder.tv_lv_day= (TextView) view.findViewById(R.id.tv_lv_day);
            viewHolder.tv_lv_title = (TextView) view.findViewById(R.id.tv_lv_title);
            viewHolder.tv_lv_content = (TextView) view.findViewById(R.id.tv_lv_content);
            viewHolder.layout = (LinearLayout) view.findViewById(R.id.Linelayout);
            view.setTag(viewHolder);
        }
        else{
            viewHolder = (ViewHolder) view.getTag();
        }
        viewHolder.tv_lv_month.setText(data.get(i).getUpdate_date().split("-")[1]);
        viewHolder.tv_lv_day.setText(data.get(i).getUpdate_date().split("-")[2]);
        viewHolder.tv_lv_title.setText(data.get(i).getTitle());
        viewHolder.tv_lv_content.setText(data.get(i).getText());

        if(data.get(i).getBackground()!=null){
            viewHolder.layout.setBackgroundColor(Color.parseColor(data.get(i).getBackground()));
        }
        return view;
    }

    class ViewHolder{
        TextView tv_lv_month;
        TextView tv_lv_day;
        TextView tv_lv_title;
        TextView tv_lv_content;
        LinearLayout layout;
    }
}

3.5 记事本的背景色设置

进入到上述提到的编辑模式2中,也就是ContentActivity中,在其底部设置更改背景色功能。这里运用到了会话框的弹出AlertDialog。设置三种颜色。点击确认后,更改数据文件中相应记事条目Note的Background属性。接着进行调用onResume();使其重新载入数据文件中的记事条目得到背景色的更改。功能截图如下:

背景选项设置成功

部分关键代码如下:

ivSettings.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                AlertDialog.Builder builder = new AlertDialog.Builder(ContentActivity.this);
                builder.setIcon(R.drawable.fab_settings);
                builder.setTitle("选择一个背景色");
                //    指定下拉列表的显示数据
                final String[] colors = {"护眼色", "薰衣淡紫", "粉粉粉"};
                builder.setItems(colors, new DialogInterface.OnClickListener()
                {
                    @Override
                    public void onClick(DialogInterface dialog, int which)
                    {
                        Toast.makeText(ContentActivity.this, "选择的背景色为:" + colors[which], Toast.LENGTH_SHORT).show();
                        switch (which){
                            case 0:
                                new NoteManager(mContext).updateBackground(title,"#C7EDCC");
                                onResume();
                                break;
                            case 1:
                                new NoteManager(mContext).updateBackground(title,"#E6E6FA");
                                onResume();
                                break;
                            case 2:
                                new NoteManager(mContext).updateBackground(title,"#FC9D9A");
                                onResume();
                                break;
                            default:
                                break;
                        }

                    }
                });
                builder.show();
            }
        });

3.6 记事本的闹铃功能

该功能的实现思路为,为闹钟设置闹铃后,在该记事条目对应得ContentActvity的右上角会显示闹铃的时间,长按该闹铃时间戳则可删除该闹铃,同时闹铃到达指定时间后,会在程序中弹出提示框并且在手机通知栏也会有消息提醒。功能截图如下:

设置闹钟闹钟设置完成通知栏响应

首先是闹铃时间的设定,这里要对ContentActivity实现相应的设置日期时间的接口。

public class ContentActivity extends AppCompatActivity
        implements DatePickerDialog.OnDateSetListener, TimePickerDialog.OnTimeSetListener{

    @Override
    public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
        alarm_year=year;
        alarm_month=monthOfYear+1;
        alarm_day=dayOfMonth;
    }

    @Override
    public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
        alarm_hour=hourOfDay;
        alarm_minute=minute;

        alarm=alarm_year+"/"+alarm_month+"/"+alarm_day+" "+alarm_hour+":"+alarm_minute;
        av.setText("Alert at "+alarm+"!");
        av.setVisibility(View.VISIBLE);

        Log.d("ContentActivity","alarm"+alarm);
        new NoteManager(mContext).updateAlarm(title,alarm);
        loadAlarm(alarm, title, 0);
        Toast.makeText(this,"Alarm will be on at "+alarm+" !",Toast.LENGTH_LONG).show();
    }


}

接着就是开始设置闹铃:

public void setAlarm(View v) {
        if(alarm.length()<=1) {
            //if no alarm clock has been set up before
            //show the current time
            Calendar c=Calendar.getInstance();
            alarm_hour=c.get(Calendar.HOUR_OF_DAY);
            alarm_minute=c.get(Calendar.MINUTE);

            alarm_year=c.get(Calendar.YEAR);
            alarm_month=c.get(Calendar.MONTH)+1;
            alarm_day=c.get(Calendar.DAY_OF_MONTH);
        }
        else {
            //show the alarm clock time which has been set up before
            int i=0, k=0;
            while(i<alarm.length()&&alarm.charAt(i)!='/') i++;
            alarm_year=Integer.parseInt(alarm.substring(k,i));
            k=i+1;i++;
            while(i<alarm.length()&&alarm.charAt(i)!='/') i++;
            alarm_month=Integer.parseInt(alarm.substring(k,i));
            k=i+1;i++;
            while(i<alarm.length()&&alarm.charAt(i)!=' ') i++;
            alarm_day=Integer.parseInt(alarm.substring(k,i));
            k=i+1;i++;
            while(i<alarm.length()&&alarm.charAt(i)!=':') i++;
            alarm_hour=Integer.parseInt(alarm.substring(k,i));
            k=i+1;i++;
            alarm_minute=Integer.parseInt(alarm.substring(k));
        }

        new TimePickerDialog(this,this,alarm_hour,alarm_minute,true).show();
        new DatePickerDialog(this,this,alarm_year,alarm_month-1,alarm_day).show();
    }

同样的,监听该view,若被响应则调用onResume();

长按删除闹铃功能这里就不再赘述了,接下来来详细讲解如何调用通知栏的通知功能。部分关键代码:

private void loadAlarm(String alarm, String title, int days) {
        int alarm_hour=0;
        int alarm_minute=0;
        int alarm_year=0;
        int alarm_month=0;
        int alarm_day=0;

        int i=0, k=0;
        while(i<alarm.length()&&alarm.charAt(i)!='/') i++;
        alarm_year=Integer.parseInt(alarm.substring(k,i));
        k=i+1;i++;
        while(i<alarm.length()&&alarm.charAt(i)!='/') i++;
        alarm_month=Integer.parseInt(alarm.substring(k,i));
        k=i+1;i++;
        while(i<alarm.length()&&alarm.charAt(i)!=' ') i++;
        alarm_day=Integer.parseInt(alarm.substring(k,i));
        k=i+1;i++;
        while(i<alarm.length()&&alarm.charAt(i)!=':') i++;
        alarm_hour=Integer.parseInt(alarm.substring(k,i));
        k=i+1;i++;
        alarm_minute=Integer.parseInt(alarm.substring(k));

        Intent intent = new Intent(ContentActivity.this, OneShotAlarm.class);
        intent.putExtra("alarm_title",title);
        PendingIntent sender = PendingIntent.getBroadcast(
                ContentActivity.this, 0, intent, 0);

        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(System.currentTimeMillis());

        Calendar alarm_time = Calendar.getInstance();
        alarm_time.set(alarm_year,alarm_month-1,alarm_day,alarm_hour,alarm_minute);

        AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
        am.set(AlarmManager.RTC_WAKEUP, alarm_time.getTimeInMillis(), sender);
    }

在上述关键代码中,可以看到在loadAlarm函数中使用了Intent来传递闹铃时间的消息给OneShotAlarm类,且是再闹铃设置完成后,构建一个PendingIntent广播,通过AlarmManager在时间到达指定的闹铃事件后,发出广播,接收到广播的OneShotAlarm类做出回应,在通知栏显示通知。

这里要先在AndroidManifest.xml设置广播接收器:

<receiver
            android:name=".utils.OneShotAlarm"
            android:process=":remote" />

接下来让我们看看OneShortAlarm类,当OneShotAlarm收到广播后,则构建通知消息并在通知栏显示。同时该类中设置了通知消息显示的内容,有该记事本程序名,记事条目的标题,该记事创建时间戳以及记事内容。通过构造Notification类实现该消息。部分关键代码如下:

public class OneShotAlarm extends BroadcastReceiver {
    private String alarm_title;
    @Override
    public void onReceive(Context context, Intent intent) {
        alarm_title=intent.getStringExtra("alarm_title");
        Toast.makeText(context,"Time UP!",Toast.LENGTH_LONG).show();
        Vibrator vb =(Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
        vb.vibrate(300);

        showNotice(context);
    }
    private void showNotice(Context context) {

        Intent intent=new Intent(context,ContentActivity.class);
        Note record = new NoteManager(context).get(alarm_title);
        new NoteManager(context).deleteAlarm(alarm_title);
        PendingIntent pi=PendingIntent.getActivity(context,0,intent,PendingIntent.FLAG_UPDATE_CURRENT);
        NotificationManager manager=(NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
        Notification notification=new NotificationCompat.Builder(context)
                .setContentTitle(record.getCreate_date())
                .setContentText(record.getText())
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(context.getResources(),R.drawable.icon))
                .setContentIntent(pi)
                .setAutoCancel(true)
                //.setStyle(new NotificationCompat.BigTextStyle().bigText(record.getMainText()))
                .setLights(Color.GREEN,1000,1000)
                .build();
        manager.notify(0,notification);
    }

}

3.7 高级搜索

在早先就有了解到一些动态搜索框现成的源码,这里则使用了一些网上开源的搜索框控件的使用。

首先需要添加第三方依赖,'com.quinny898.library.persistentsearch:library:1.1.0-SNAPSHOT'就可以使用了。该搜索框还支持语音输入识别,但这里因为在设计时并没有考虑语音识别功能,所以这里就弃用了该功能。

附上功能截图:

<com.quinny898.library.persistentsearch.SearchBox
        android:layout_width="wrap_content"
        android:id="@+id/searchbox"
        android:layout_height="wrap_content"/>

设置完成后,需要对显示搜索到的记事条目,内容,及搜索到的记事条目总和进行显示,这里布局设计如下:

<LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        <ListView
            android:id="@+id/history_lis_search"
            android:layout_marginLeft="20dp"
            android:layout_marginRight="20dp"
            android:layout_marginBottom="5dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@drawable/border_bottom_null">
        </ListView>
    </LinearLayout>

    <ListView
        android:id="@+id/content_lis_search"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/border_bottom_null">
    </ListView>

    <TextView
        android:id="@+id/bottom_search"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:gravity="center_horizontal" />

接着则是代码的实现,实现该功能代码网上有很多现成的实例,这里实现起来并没有看起来的那么复杂,稍微学习后则可写出自己想要的代码:

public class SearchActivity extends AppCompatActivity {
    private Context mContext;
    private SearchBox sbSearch;
    private TextView tvBottom;
    //数据列表
    private List<Note> listSearch;
    //结果列表
    private List<Note> listResult;
    private ListView mSearchResult ;
    private SearchAdapter mResultAdapter ;

    private ListView mHistory ;
    private HistoryAdapter mHistoryAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_search);
        mContext = this;
        bindViews();
    }

    @Override
    protected void onResume() {
        super.onResume();
        initData();
        init();
        initSearchView();
        initHistory();
        updateBottom();
    }

    private void initData() {
        //本地可供检索数据获取,每次resume就要重新渲染
        listSearch = new NoteManager(mContext).getNoteList();
    }

    private void bindViews() {
        mSearchResult = (ListView) findViewById(R.id.content_lis_search);
        sbSearch = (SearchBox) findViewById(R.id.searchbox);
        tvBottom = (TextView) findViewById(R.id.bottom_search);
        mHistory = (ListView) findViewById(R.id.history_lis_search);
    }

    public void init(){
        listResult = new ArrayList<>();
        mResultAdapter = new SearchAdapter(SearchActivity.this, listResult);
        mSearchResult.setAdapter(mResultAdapter);
        mSearchResult.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                manageKeyBoard();
                NoteManager noteManager=new NoteManager(mContext);
                InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
                boolean isOpen = imm.isActive();
                if (isOpen) imm.hideSoftInputFromWindow(view.getWindowToken(), 0); //强制隐藏键盘
                noteManager.ItemClick(listResult.get(position));
            }
        });
    }

    private void initSearchView(){
        sbSearch.enableVoiceRecognition(this);
        sbSearch.setMenuListener(new SearchBox.MenuListener(){
            @Override
            public void onMenuClick() {
                reBack();
            }
        });
        sbSearch.setSearchListener(new SearchBox.SearchListener(){

            @Override
            public void onSearchOpened() {
                //Use this to tint the screen
            }
            @Override
            public void onSearchClosed() {
                //Use this to un-tint the screen
            }
            @Override
            public void onSearchTermChanged(String term) {
                search(term);
                updateBottom();

                if(listResult.size()==0){
                    mHistory.setVisibility(View.VISIBLE);
                }else {
                    mHistory.setVisibility(View.GONE);
                }
            }
            @Override
            public void onSearch(String searchTerm) {
                search(searchTerm);
                saveHistory(searchTerm);
                initHistory();
                updateBottom();
            }
            @Override
            public void onResultClick(SearchResult result) {
                //React to a result being clicked
            }
            @Override
            public void onSearchCleared() {
                //Called when the clear button is clicked
            }

        });
    }

    private void initHistory(){

        final List<String> history = getHistory();
        mHistoryAdapter = new HistoryAdapter(SearchActivity.this,history);
        mHistory.setAdapter(mHistoryAdapter);
        mHistory.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                sbSearch.populateEditText(history.get(position));
                sbSearch.setSearchString(history.get(position));
            }
        });

    }

    private void search(String newText){

        //若搜索内容为空
        if(newText.isEmpty()){
            listResult.clear();
        }
        else{

            for (Note note : listSearch) {//开始搜索
                //搜索内容搜索到相关
                if (note.getTitle().contains(newText) || note.getText().contains(newText)) {

                    if(listResult.indexOf(note)==-1) {//且 结果集内不含有此内容
                        listResult.add(note);
                    }
                }else{
                    //搜索内容搜索不到相关 检测是否之前有加入结果集 有则删除
                    if(listResult.indexOf(note)!=-1) {
                        listResult.remove(note);
                    }
                }
            }
        }
        mResultAdapter.notifyDataSetChanged();
    }

    private ArrayList<String> getHistory() {
        SharedPreferences reader = getSharedPreferences("history", MODE_PRIVATE);
        String data = reader.getString("data_history", "");
        ArrayList<String> history = new ArrayList<>();
        String [] get=  data.split("\\|");
        for( String str:get){
            if(! history.contains(str) && !StringUtil.isEmpty(str)){
                history.add(str);
            }
        }
        return history;
    }

    private void saveHistory(String s){
        StringBuilder sb = new StringBuilder();
        SharedPreferences.Editor editor = getSharedPreferences("history",MODE_PRIVATE).edit();
        for (String str: getHistory()){
            sb.append(str);
            sb.append("|");
        }
        sb.append(s);
        editor.putString("data_history",sb.toString());
        editor.apply();
    }

    private void updateBottom(){
        if(sbSearch.getSearchText().trim().isEmpty()){
            gone_Bottom();
            return;
        }
        tvBottom.setVisibility(View.VISIBLE);
        tvBottom.setText("找到了 "+ listResult.size() +" 条记录");

    }
    private void gone_Bottom(){
        TextView bottom = (TextView) findViewById(R.id.bottom_search);
        bottom.setVisibility(View.GONE);
    }

    private void reBack(){
        finish();
        overridePendingTransition(R.anim.in_from_left, R.anim.out_from_right);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        switch(keyCode){
            case KeyEvent.KEYCODE_BACK:
                finish();
                overridePendingTransition(R.anim.in_from_left, R.anim.out_from_right);
                break;
        }
        return super.onKeyUp(keyCode, event);
    }
    private void manageKeyBoard() {
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
    }
}

4. 总结

这次完成该记事本的完成算是对先前学到知识的应用以及进阶,如Intent意图的使用,adapter适配器的使用,等等,对其掌握有了更进一步的提高,同时对程序UI美化和程序数据存储有了全新的了解与把握。也了解到市面上有很多开源的控件可供使用,还需要学习的东西还有很多很多。谢谢。

参考资料:

https://blog.youkuaiyun.com/zhouchen1998/article/details/83628218

https://blog.youkuaiyun.com/sf_zhang26/article/details/54803691

https://blog.youkuaiyun.com/zhouchen1998/article/details/82995526 

本文作者:林嘉伟

原文链接:https://blog.youkuaiyun.com/DieForCode/article/details/90602224

评论 26
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值