最近刚看完了《第一行代码》这本书,趁着手热,写了一个日记本App,App虽然挺简单的,但对于一个刚学Android开发的小白来说,开发起来还是很艰难的,现在项目已经完成,在这里跟大家分享下,希望一起交流进步!
先上图,顺便介绍下功能:
这是登录界面,简洁明了,不多说了哈
这是登录之后的主界面,采用的是卡片式布局
这是编辑界面
当长按时日记右上方会出现复选框,选中点击删除按钮即可进行删除操作
好了,功能介绍完毕,下面介绍下主要的实现方法吧
LoignActivity(实现登录操作):
package com.example.a15711.diarypractice;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.mainactivity.MainActivity;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.telephony.CellIdentityCdma;
import android.text.method.HideReturnsTransformationMethod;
import android.text.method.PasswordTransformationMethod;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;
public class LoginActivity extends AppCompatActivity{
private SharedPreferences pref;//定义一个SharedPreferences对象
private SharedPreferences.Editor editor;//调用SharedPreferences对象的edit()方法来获取一个SharedPreferences.Editor对象,用以添加要保存的数据
private Button login;//登录按钮
private EditText adminEdit;//用户名输入框
private EditText passwordEdit;//密码输入框
private CheckBox savePassword;//是否保存密码复选框
private CheckBox showPassword;//显示或隐藏密码复选框
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//将背景图与状态栏融合到一起,只支持Android5.0以上的版本
if(Build.VERSION.SDK_INT>=21){
View decorView=getWindow().getDecorView();
//布局充满整个屏幕
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
//设置状态栏为透明
getWindow().setStatusBarColor(Color.TRANSPARENT);
}
setContentView(R.layout.activity_login);
//获取各组件或对象的实例
pref= PreferenceManager.getDefaultSharedPreferences(this);
login=(Button)findViewById(R.id.login_button);
adminEdit=(EditText)findViewById(R.id.admin);
passwordEdit=(EditText)findViewById(R.id.password);
savePassword=(CheckBox)findViewById(R.id.save_password);
showPassword=(CheckBox)findViewById(R.id.show_password);
//获取当前“是否保存密码”的状态
final boolean isSave=pref.getBoolean("save_password",false);
//当“是否保存密码”勾选时,从SharedPreferences对象中读出保存的内容,并显示出来
if(isSave){
String account=pref.getString("account","");
String password=pref.getString("password","");
adminEdit.setText(account);
passwordEdit.setText(password);
//把光标移到文本末尾处
adminEdit.setSelection(adminEdit.getText().length());
passwordEdit.setSelection(passwordEdit.getText().length());
savePassword.setChecked(true);
}
//用户点击登录时的处理事件
login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//读出用户名和密码并判断是否正确
String account=adminEdit.getText().toString();
String password=passwordEdit.getText().toString();
//用户名和密码正确
if(account.equals("admin")&&password.equals("123456")){
editor=pref.edit();
//“是否保存密码”勾选
if(savePassword.isChecked()){
editor.putBoolean("save_password",true);
editor.putString("account",account);
editor.putString("password",password);
}
else{
editor.clear();
}
//提交完成数据存储
editor.apply();
//显示登录成功并跳转到主界面活动
Toast.makeText(LoginActivity.this,"登录成功",Toast.LENGTH_SHORT).show();
Intent intent=new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
//结束当前活动
finish();
}
//用户名或密码错误
else{
Toast.makeText(LoginActivity.this,"登录失败,请重新输入!",Toast.LENGTH_SHORT).show();
}
}
});
//用户点击'显示密码'复选框
showPassword.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(showPassword.isChecked()){
showOrHide(passwordEdit,true);
}else{
showOrHide(passwordEdit,false);
}
}
});
}
//当用户离开活动时,检测是否勾选记住密码,若勾选则保存用户输入的用户名及密码
@Override
protected void onDestroy() {
super.onDestroy();
editor=pref.edit();
String account=adminEdit.getText().toString();
String password=passwordEdit.getText().toString();
if(savePassword.isChecked()){
editor.putBoolean("save_password",true);
editor.putString("account",account);
editor.putString("password",password);
}else{
editor.clear();
}
editor.apply();
}
//显示或隐藏密码
private void showOrHide(EditText passwordEdit,boolean isShow){
//记住光标开始的位置
int pos = passwordEdit.getSelectionStart();
if(isShow){
passwordEdit.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
}else{
passwordEdit.setTransformationMethod(PasswordTransformationMethod.getInstance());
}
passwordEdit.setSelection(pos);
}
}
这个活动挺简单的,要注意的地方不多,一个是SharedPreferences ,用它来保存用户名和密码比较方便;另一个就是showOrHide方法,用它来实现显示或隐藏密码,这里初始密码为隐藏状态,需要在布局中密码属性中设置:android:password=“true”;
MainActivity:(日记展示界面)
package android.mainactivity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.editactivity.EditActivity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.Toast;
import com.example.a15711.diarypractice.R;
import org.litepal.crud.DataSupport;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class MainActivity extends AppCompatActivity {
//要删除的项的标志HashMap数组
HashMap<Integer,String> str=new HashMap<>();
//设置是否在onStart中更新数据源的标志
private int update=0;
//存储diary对象的数组
private List<diary> diaryList=new ArrayList<>();
//适配器
private diaryAdapter adapter;
//是否处于多选删除状态
// 设置这个变量是为了让区分正常点击和多选删除时的点击事件
//以及长按状态时不再响应长按事件
private boolean isDeleteState=false;
//网格布局管理器
GridLayoutManager gridLayoutManager=new GridLayoutManager(this,2);
//线性布局管理器
LinearLayoutManager linearLayoutManager=new LinearLayoutManager(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//从数据库中获取日记
diaryList= DataSupport.findAll(diary.class);
RecyclerView recyclerView=(RecyclerView)findViewById(R.id.recycler_view);
if(diaryList.size()>0){
//数据库中有日记记录,以网格布局展示
recyclerView.setLayoutManager(gridLayoutManager);
}else{
//数据库中没有日记记录,用线性布局显示“无数据”
recyclerView.setLayoutManager(linearLayoutManager);
}
//适配器的初始化,第一个参数传入数据源,第二个参数false表示正常状态;true表示多选删除状态
adapter=new diaryAdapter(diaryList,false);
recyclerView.setAdapter(adapter);
//初始化‘新建’和‘删除’按钮
Button build=(Button)findViewById(R.id.build_button);
Button delete=(Button)findViewById(R.id.delete_button);
//点击‘新建’按钮时,跳转到编辑日记的活动
build.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(MainActivity.this, EditActivity.class);
intent.putExtra("diaryContent","");//传入日记内容,这里为空
intent.putExtra("signal",0);//传入‘新建’标志:0
startActivity(intent);
}
});
//点击‘删除按钮时,执行删除操作
delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
deleteSelections();
}
});
//为RecyclerView添加点击事件响应和长按事件响应
recyclerView.addOnItemTouchListener(new RecyclerItemClickListener(this, recyclerView, new RecyclerItemClickListener.OnItemClickListener() {
//点击事件响应
@Override
public void onItemClick(View view, int position) {
if(!isDeleteState&&diaryList.size()>0){
// 普通点击事件
diary mDiary=diaryList.get(position);
String diaryContent=mDiary.getContent().toString();
String diaryTime=mDiary.getTime().toString();
Intent intent=new Intent(MainActivity.this,EditActivity.class);
//传递内容
intent.putExtra("diaryContent",diaryContent);
//传入修改标志1:表示修改原有日记内容
intent.putExtra("signal",1);
startActivity(intent);
}else if (diaryList.size()>0){
//长按进入多选状态后的点击事件
CheckBox checkBox = (CheckBox) view.findViewById(R.id.check_box);
if (checkBox.isChecked()) {
checkBox.setChecked(false);
str.remove(position);
} else {
str.put(position,diaryAdapter.mDiaryList.get(position).getContent());
checkBox.setChecked(true);
}
}
}
@Override
public void onItemLongClick(View view, int position) {
// 长按事件
if(!isDeleteState&&diaryList.size()>0){
isDeleteState=true;
str.clear();
//把当前选中的的复选框设置为选中状态
diaryAdapter.isSelected.put(position,true);
//把所有的CheckBox显示出来
RecyclerView recyclerView=(RecyclerView)findViewById(R.id.recycler_view);
//第二个参数为true表示长按进入多选删除状态时的适配器初始化
adapter=new diaryAdapter(diaryList,true);
recyclerView.setAdapter(adapter);
str.put(position,diaryAdapter.mDiaryList.get(position).getContent());
}
}
}));
}
//用户返回该活动时
@Override
protected void onStart() {
super.onStart();
isDeleteState=false;
if(update==1){
diaryList.clear();
RecyclerView recyclerView=(RecyclerView)findViewById(R.id.recycler_view);
List<diary> data=DataSupport.findAll(diary.class);
for(diary mdiary:data){
diaryList.add(mdiary);
}
if(diaryList.size()>0){
recyclerView.setLayoutManager(gridLayoutManager);
}else{
recyclerView.setLayoutManager(linearLayoutManager);
}
adapter=new diaryAdapter(diaryList,false);
recyclerView.setAdapter(adapter);
}
}
@Override
protected void onStop() {
super.onStop();
update=1;//更新数据源
}
//执行删除的函数
private void deleteSelections() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
if (str.size()==0) {
builder.setTitle("提示").setMessage("当前未选中项目").setPositiveButton("确认", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
RecyclerView recyclerView=(RecyclerView)findViewById(R.id.recycler_view);
adapter=new diaryAdapter(diaryList,false);
recyclerView.setAdapter(adapter);
isDeleteState=false;
str.clear();
}
}).create().show();
} else {
builder.setTitle("提示");
builder.setMessage("确认删除所选日记?");
builder.setPositiveButton("确认", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
for(int i:str.keySet()){
DataSupport.deleteAll(diary.class,"content=?",str.get(i));
}
diaryList.clear();
List<diary> data=DataSupport.findAll(diary.class);
for(diary mdiary:data){
diaryList.add(mdiary);
}
adapter.notifyDataSetChanged();
RecyclerView recyclerView=(RecyclerView)findViewById(R.id.recycler_view);
if(diaryList.size()>0){
recyclerView.setLayoutManager(gridLayoutManager);
}else{
recyclerView.setLayoutManager(linearLayoutManager);
}
adapter=new diaryAdapter(diaryList,false);
recyclerView.setAdapter(adapter);
isDeleteState=false;
str.clear();
Toast.makeText(MainActivity.this,"删除成功!",Toast.LENGTH_SHORT).show();
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
RecyclerView recyclerView=(RecyclerView)findViewById(R.id.recycler_view);
adapter=new diaryAdapter(diaryList,false);
recyclerView.setAdapter(adapter);
isDeleteState=false;
str.clear();
}
});
builder.create().show();
}
}
}
diaryAdapter(适配器):
package android.mainactivity;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.TextView;
import com.example.a15711.diarypractice.R;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
/**
* Created by 15711 on 2018/10/16.
*/
public class diaryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Boolean isDuoXuan;//是否多选的标志
private Context mContext;//上下文
public static List<diary> mDiaryList=new ArrayList<>();//数据项列
//键为RecyclerView中各子项的position,值为该位置复选框的选中状态
public static HashMap<Integer,Boolean> isSelected=new HashMap<>();
//这是有日记展示的Holder
static class ViewHolder extends RecyclerView.ViewHolder{
CardView cardView;
TextView diaryContent;
TextView diaryTime;
CheckBox checkBox;
public ViewHolder(View view){
super(view);
cardView=(CardView)view;
diaryContent=(TextView)view.findViewById(R.id.diary_content);
diaryTime=(TextView)view.findViewById(R.id.diary_time);
checkBox=(CheckBox)view.findViewById(R.id.check_box);
}
}
//这是无日记展示的Holder
static class EmptyViewHolder extends RecyclerView.ViewHolder{
View empty_view;
TextView textView;
public EmptyViewHolder(View view){
super(view);
empty_view=view;
textView=(TextView)view.findViewById(R.id.empty_text);
}
}
//适配器的构造函数
public diaryAdapter(List<diary> diaryList,boolean isDuoXuan){
mDiaryList=diaryList;
this.isDuoXuan=isDuoXuan;
}
//数据源是否为空,为空返回-1
@Override
public int getItemViewType(int position) {
if(mDiaryList.size()<=0){
return -1;
}
return super.getItemViewType(position);
}
//创建ViewHolder并返回
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(mContext==null){
mContext=parent.getContext();
}
//数据源为空时返回空的子项布局
if(viewType==-1){
View view=LayoutInflater.from(mContext).inflate(R.layout.diary_empty_item,parent,false);
return new EmptyViewHolder(view);
}
//数据源不为空时返回卡片布局
View view= LayoutInflater.from(mContext).inflate(R.layout.diary_item,parent,false);
return new ViewHolder(view);
}
//具体的处理逻辑
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
//holder为卡片布局的holder
if (holder instanceof ViewHolder) {
if(isDuoXuan){
((ViewHolder) holder).checkBox.setVisibility(View.VISIBLE);
//当isSelected中有该位置CheckBox的显示状态时就加载,没有就设为false
if(isSelected.containsKey(position)){
((ViewHolder) holder).checkBox.setChecked(isSelected.get(position));
}else{
isSelected.put(position,false);
}
}else{
//单选状态时清空isSelected,并设置所有复选框不可见
isSelected.clear();
((ViewHolder) holder).checkBox.setVisibility(View.GONE);
}
diary mDiary=mDiaryList.get(position);
((ViewHolder) holder).diaryContent.setText(mDiary.getContent());
((ViewHolder) holder).diaryTime.setText(mDiary.getTime());
}
}
//告诉适配器有多少个项,以便留出足够的空间
@Override
public int getItemCount() {
return mDiaryList.size()>0?mDiaryList.size():1;
}
}
RecyclerItemClickListener(RecyclerView的监听器类):
package android.mainactivity;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
//为RecyclerView设置监听事件
public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener {
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
private OnItemClickListener mListener;
private GestureDetector mGestureDetector;
public RecyclerItemClickListener(Context context, final RecyclerView recyclerView, OnItemClickListener listener) {
mListener = listener;
mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapUp(MotionEvent e) {
return true;
}
@Override
public void onLongPress(MotionEvent e) {
View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());
if (childView != null && mListener != null) {
mListener.onItemLongClick(childView, recyclerView.getChildAdapterPosition(childView));
}
}
});
}
@Override
public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
View childView = view.findChildViewUnder(e.getX(), e.getY());
if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) {
mListener.onItemClick(childView, view.getChildAdapterPosition(childView));
}
return false;
}
@Override
public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
}
这个活动的实现要配合一个RecyclerView适配器和一个RecyclerView监听器,以对点击事件及长按事件进行监听和处理,相对来说比较复杂,但耐心看的话还是很容易理解的,对照注释相信大家都可以看懂,在这里我就不多说了
EditActivity(编辑界面):
package android.editactivity;
import android.app.AlertDialog;
import android.content.ContentValues;
import android.content.DialogInterface;
import android.content.Intent;
import android.mainactivity.KeyboardUtils;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.mainactivity.diary;
import android.view.View;
import android.widget.EditText;
import com.example.a15711.diarypractice.R;
import org.litepal.crud.DataSupport;
import java.text.SimpleDateFormat;
public class EditActivity extends AppCompatActivity {
//接收上个活动传入的日记内容
private String diaryContent;
//接收上个活动传入的标志
private int signal=0;
//加载菜单
public boolean onCreateOptionsMenu(Menu menu){
getMenuInflater().inflate(R.menu.edit_toolbar,menu);
return true;
}
//菜单项的点击事件
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
//点击保存
case R.id.save_button: {
EditText editText=(EditText)findViewById(R.id.edit_content);
String content=editText.getText().toString();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy年MM月dd日");
String time=sdf.format(new java.util.Date());
diary mDiary=new diary(content,time);
//点击’新建‘后编辑的内容就存储
if(signal==0) {
mDiary.save();
//防止连续点击’存储‘按钮连续存储一样的内容
signal=3;
}
//更新原有内容的就只更新
else{
//防止连续点击’存储‘按钮连续存储一样的内容
signal=3;
ContentValues values = new ContentValues();
values.put("time", mDiary.getTime().toString());
DataSupport.updateAll(diary.class,values,"content=?",diaryContent);
values.put("content", mDiary.getContent().toString());
DataSupport.updateAll(diary.class,values,"content=?",diaryContent);
}
//隐藏光标并收起键盘
editText.setCursorVisible(false);
KeyboardUtils.hideKeyboard(this);
break;
}
//点击返回
case android.R.id.home:{
//已经保存的直接返回
if(signal==3){
finish();
}
//未保存的提示是否保存
else{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("");
builder.setMessage("保存此次修改吗?");
builder.setPositiveButton("确认", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
EditText editText=(EditText)findViewById(R.id.edit_content);
String content=editText.getText().toString();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy年MM月dd日");
String time=sdf.format(new java.util.Date());
diary mDiary=new diary(content,time);
if(signal==0){
mDiary.save();
}else{
ContentValues values = new ContentValues();
values.put("time", mDiary.getTime().toString());
DataSupport.updateAll(diary.class,values,"content=?",diaryContent);
values.put("content", mDiary.getContent().toString());
DataSupport.updateAll(diary.class,values,"content=?",diaryContent);
}
finish();
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
});
builder.create().show();
}
break;
}
default:
}
return true;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit);
//接收由MainActivity传来的日记信息
Intent intent=getIntent();
diaryContent=intent.getStringExtra("diaryContent");
signal=intent.getIntExtra("signal",0);
Toolbar toolbar=(Toolbar)findViewById(R.id.edit_toolbar);
toolbar.setTitle("");
setSupportActionBar(toolbar);
ActionBar actionBar=getSupportActionBar();
if(actionBar!=null){
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeAsUpIndicator(R.drawable.ic_back);
}
final EditText editText=(EditText)findViewById(R.id.edit_content);
editText.setText(diaryContent);
//光标放文本后面
editText.setSelection(editText.getText().length());
editText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
editText.setCursorVisible(true);
}
});
}
}
这个活动主要实现了保存和返回两个按钮的功能,结合注释和之前两个活动的介绍,相信你也能容易理解的
好了,内容也都介绍的差不多了,下面来总结一下这次开发的经验和教训吧。
讲真的在开发过程中,有好几次遇到不能解决的bug时,差点就放弃了,甚至会怀疑是不是代码诚心跟你作对,最后结果证明还是自己在跟自己作对,很多时候只是一些小的细节没有注意到,就可能导致很多匪夷所思的错误,所以思维一定要严谨!不能想当然,不然真的会吃很多苦头。
开发程序的时候一定要有思路,不能想到哪写到哪,那样的话很没有效率而且写出来的代码很乱,在写代码的过程中尽量做到多写注释,这样当你有问题回过头来找错误时会有很大帮助,不然的话你会无从找起,从而浪费更多时间和精力。
开发的过程的确很苦,但只要坚持还是可以成功的,当你的程序成功的跑起来的时候,相信你的内心一定是充满喜悦与满足的。
通过这次的练习我也发现了自己很多不足,包括调试代码找问题的能力,以及深入理解问题并想出解决办法的能力等等,这些都需要自己去提高。
谢谢浏览!
源代码下载