一直有想写博客的想法,无奈自己太懒。现在疫情期间,不开学,上网课,日子过得更加浑浑噩噩,下定决心开发一个记录学习生活的小项目,加以博客的形式记录学习历程,帮助自己提升水准。

废话说完,大概看一下项目目前的样子:


设计了一个比较普通的功能:

  • 在计划页面设置每日的计划,并在主页显示,同时在主页可以点击计划将其划掉,代表今日已完成该计划。

要实现这个功能,我认为几个比较重要的步骤是:

  • 设计数据库及数据表
  • 自定义Dialog以显示修改计划的对话框
  • 自定义Adapter类在RecyclerView上渲染数据

总结
1.数据库方面:

我使用的是DBFlow来存储和操作数据。
官方目前的版本是5.0.0-alpha1,github地址:https://github.com/agrosner/DBFlow

由于个人水平原因,使用时出现一些暂时无法解决的异常,随即在网上查阅发现DBFlow(4.2.4)版本的使用方法,故在此使用的是4.2.4版本。

参考博客:
https://www.cnblogs.com/xxdh/p/9282504.html
https://www.cnblogs.com/ChenJanson/p/5363420.html

配置DBFlow:
在项目目录的build.gradle中加入:

allprojects {repositories {...maven { url "https://jitpack.io" }}
}

在工程目录的build.gradle中加入:

def dbflow_version = "4.2.4"
dependencies {...annotationProcessor "com.github.Raizlabs.DBFlow:dbflow-processor:$dbflow_version"implementation "com.github.Raizlabs.DBFlow:dbflow-core:$dbflow_version"implementation "com.github.Raizlabs.DBFlow:dbflow:$dbflow_version"
}

创建数据库:

@Database(name = MyDatabase.NAME,version = MyDatabase.VERSION)
public class MyDatabase{//数据库名称public static final String NAME = "MyDatabase";//版本号,因为更新过表的字段所以版本号在此为"3"public static final int VERSION = 3;//更新数据表时需要的操作,具体可以参考我列出的博客@Migration(version = 3,database = MyDatabase.class)public static class Migration_3_Plan extends AlterTableMigration<Plan>{public Migration_3_Plan(Class<Plan> table) {super(table);}//为表<Plan>添加了一个名为"isFinished"的字段@Overridepublic void onPreMigrate() {addColumn(SQLiteType.get(boolean.class.getName()),"isFinished");}}
}

创建Model

@Table(database = MyDatabase.class)
//继承BaseModel,BaseModel包含了基本的数据库操作
public class Plan extends BaseModel implements Serializable {@PrimaryKeypublic int id;//计划的内容@Columnpublic String content;//计划的日期@Columnpublic Date date;//该计划是否是日常的@Columnpublic Boolean isDaily;//新增的字段,记录计划是否已经完成,默认值为false@Columnpublic Boolean isFinished = false;
}

初始化DBFlow

public class MyApplication extends Application {private static Context mContext;@Overridepublic void onCreate() {super.onCreate();mContext = getApplicationContext();//初始化DBflowFlowManager.init(this);}public static Context getContext(){return mContext;}
}

之后就可以进行数据库的操作了。

2.自定义对话框(包含自定义适配器):

当点击计划页面的“修改”按钮时弹出,用于修改计划内容。以下是实现时需注意的点:

  • 获取所有想要自定义的组件,并通过setXX方法以在外部自定义
  • 对话框中列表数据发生变化时,需调用适配器(adapter)的通知(notify)方法通知相应的数据发生了变化,嫌麻烦可直接使用adapter.notifyDataSetChanged()方法,只是这样做不会有删除和添加的动画效果以及更耗资源,虽然这里数据量比较少不会有太大损耗就是了

参考博客
https://blog.csdn.net/qq_33919497/article/details/79877430?utm_source=blogxgwz3

public class PlanDialog extends Dialog {private LinearLayout container;         //布局容器private Button confirmBtn,cancelBtn;    //确定和取消按钮private RecyclerView planRv;            //计划列表private TextView addTv;                 //添加计划的文本按钮private TextView titleTv;private int backgroundId;                        //自定义背景图片的资源IDprivate String confirmText,cancelText,addText;   //对应按钮显示的文本private String titleText;                        //对话框的标题文本private RecyclerView.Adapter adapter;            //计划列表的适配器private onConfirmBtnClickListener onConfirmBtnClickListener;    //监听确认按钮的点击事件private onCancelBtnClickListener onCancelBtnClickListener;      //监听取消按钮的点击事件private onModifyTextClickListener onModifyTextClickListener;    //监听修改按钮的点击事件private Context mContext;public PlanDialog(@NonNull Context context) {super(context);mContext = context;}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.dialog_plan);//设置点击空白处是否关闭对话框setCanceledOnTouchOutside(false);//初始化界面组件initView();//初始化数据initData();//初始化按钮的点击事件initEvent();}/*** 设置对话框背景图片* @param resourceId 自定义背景图片资源ID*/public void setBackground(int resourceId){backgroundId = resourceId;}/*** 设置对话框标题* @param title 自定义标题内容*/public void setTitle(String title){titleText = title;}/*** 设置列表的适配器*/public void setAdapter(RecyclerView.Adapter adapter){this.adapter = adapter;}/*** 设置确认按钮的显示内容与监听* @param text 自定义按钮文本* @param onConfirmBtnClickListener 自定义监听器*/public void setOnConfirmBtnClickListener(String text,onConfirmBtnClickListener onConfirmBtnClickListener){if(text != null){confirmText = text;}this.onConfirmBtnClickListener = onConfirmBtnClickListener;}/*** 设置取消按钮的显示内容与监听* @param text  自定义按钮文本* @param onCancelBtnClickListener  自定义监听器*/public void setOnCancelBtnClickListener(String text,onCancelBtnClickListener onCancelBtnClickListener){if(text != null){cancelText = text;}this.onCancelBtnClickListener = onCancelBtnClickListener;}/*** 设置修改按钮的监听* @param onModifyTextClickListener  自定义监听器*/public void setOnModifyTextClickListener(String text,onModifyTextClickListener onModifyTextClickListener){if(text != null){addText = text;}this.onModifyTextClickListener = onModifyTextClickListener;}/*** 初始化界面组件*/private void initView(){container = findViewById(R.id.plan_container);confirmBtn = findViewById(R.id.confirm_btn);cancelBtn = findViewById(R.id.cancel_btn);planRv = findViewById(R.id.plan_rv);addTv = findViewById(R.id.plan_add_tv);titleTv = findViewById(R.id.plan_title_tv);LinearLayoutManager layoutManager = new LinearLayoutManager(mContext);layoutManager.setOrientation(LinearLayoutManager.VERTICAL);planRv.setLayoutManager(layoutManager);}/*** 初始化数据*/private void initData(){if(backgroundId != 0){container.setBackgroundResource(backgroundId);}if(titleText != null){titleTv.setText(titleText);}if(confirmText != null){confirmBtn.setText(confirmText);}if(cancelText != null){cancelBtn.setText(cancelText);}if(addText != null){addTv.setText(addText);}if(adapter != null){planRv.setAdapter(adapter);}}/*** 初始化按钮的点击事件*/private void initEvent(){confirmBtn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if(onConfirmBtnClickListener != null){onConfirmBtnClickListener.onConfirmBtnClick();;}}});cancelBtn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if(onCancelBtnClickListener != null){onCancelBtnClickListener.onCancelBtnClick();}}});addTv.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if(onModifyTextClickListener != null){onModifyTextClickListener.onModifyTextClick();}}});}/*** 设置按钮被点击的接口*/public interface onConfirmBtnClickListener{void onConfirmBtnClick();}public interface onCancelBtnClickListener{void onCancelBtnClick();}public interface onModifyTextClickListener{void onModifyTextClick();}/*** 显示对话框*/@Overridepublic void show() {super.show();//设置对话框的大小,要设置在show()方法之后WindowManager.LayoutParams layoutParams = getWindow().getAttributes();layoutParams.height = ScreenUtils.getScreenHeight(mContext) * 3/5;layoutParams.width = ScreenUtils.getScreenWidth(mContext) * 5/6;getWindow().setAttributes(layoutParams);}/*** 通知列表更新对应位置的数据*/public void notifyInserted(int position){if (adapter != null) {adapter.notifyItemInserted(position);}}public void notifyChanged(int position){if (adapter != null) {adapter.notifyItemChanged(position);}}public void notifyRemoved(int position){if (adapter != null) {adapter.notifyItemRemoved(position);}}public void updateAll(){if (adapter != null) {adapter.notifyDataSetChanged();}}
}

布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutandroid:id="@+id/plan_container"xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:padding="8dp"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:id="@+id/plan_title_tv"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentStart="true"android:textColor="@color/black"android:text="计划"/><TextViewandroid:id="@+id/plan_add_tv"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentEnd="true"android:textColor="@color/red"android:text="添加"/></RelativeLayout><android.support.v7.widget.RecyclerViewandroid:id="@+id/plan_rv"android:layout_width="match_parent"android:layout_height="240dp"></android.support.v7.widget.RecyclerView><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid:id="@+id/cancel_btn"android:layout_width="120dp"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:layout_alignParentStart="true"android:background="@drawable/selector_btn_plan"android:text="取消"/><Buttonandroid:id="@+id/confirm_btn"android:layout_width="120dp"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:layout_alignParentEnd="true"android:background="@drawable/selector_btn_plan"android:text="保存"/></RelativeLayout></LinearLayout>

使用自定义对话框(部分代码省略),此处可先无视自定义适配器的内容(注释内容)

    private void showDialog(){//实例化自定义对话框final PlanDialog dailyPlanDialog = new PlanDialog((getActivity());//设置对话框标题dailyPlanDialog.setTitle("每日计划");//设置背景图片dailyPlanDialog.setBackground(R.drawable.pic_bg_dialog);//        //设置列表适配器
//        final DailyPlanSettingAdapter settingAdapter = new DailyPlanSettingAdapter(getActivity(),R.layout.item_plan_setting,temporaryList);
//
//        /* 设置自定义监听器 */
//        //计划文本更新的监听器
//        settingAdapter.setOnTextChangedListener(new DailyPlanSettingAdapter.onTextChangedListener() {//            @Override
//            public void onTextChanged(int position, Editable text) {//                Plan plan = temporaryList.get(position);
//                plan.content = text.toString();
//                temporaryList.set(position,plan);
//            }
//        });
//
//        //删除按钮点击事件监听器
//        settingAdapter.setOnDeleteBtnClickListener(new DailyPlanSettingAdapter.onDeleteBtnClickListener() {//            @Override
//            public void onDeleteBtnClick(int position) {//                temporaryList.remove(position);
//                //通知Dialog中的适配器更新内容
//                dailyPlanDialog.notifyRemoved(position);
//                while (position < temporaryList.size()){//                    dailyPlanDialog.notifyChanged(position);
//                    position++;
//                }
//            }
//        });
//        dailyPlanDialog.setAdapter(settingAdapter);//设置按钮的文本及监听事件//保存按钮dailyPlanDialog.setOnConfirmBtnClickListener("保存",new PlanDialog.onConfirmBtnClickListener() {@Overridepublic void onConfirmBtnClick() {//执行需要的操作...//关闭对话框dailyPlanDialog.dismiss();}});//取消按钮dailyPlanDialog.setOnCancelBtnClickListener("取消", new PlanDialog.onCancelBtnClickListener() {@Overridepublic void onCancelBtnClick() {//关闭对话框dailyPlanDialog.dismiss();}});//添加按钮dailyPlanDialog.setOnModifyTextClickListener("添加",new PlanDialog.onModifyTextClickListener() {@Overridepublic void onModifyTextClick() {//执行需要的操作...}});//显示对话框dailyPlanDialog.show();}

自定义适配器(设置计划)

public class DailyPlanSettingAdapter extends RecyclerView.Adapter<DailyPlanSettingAdapter.ViewHolder>{private List<Plan> mPlanList;     //需要渲染的列表数据private int mLayout;            //需要渲染的列表布局idprivate Context mContext;private onDeleteBtnClickListener onDeleteBtnClickListener;  //监听删除按钮的点击事件private onTextChangedListener onTextChangedListener;        //监听文本更新事件/*** 构造方法* @param context   应用上下文* @param layout    布局资源id* @param planList  列表数据*/public DailyPlanSettingAdapter(Context context, int layout, List<Plan> planList){mContext = context;mLayout = layout;mPlanList = planList;}/*** ViewHolder*/static class ViewHolder extends RecyclerView.ViewHolder {EditText planEt;Button deleteBtn;ViewHolder(@NonNull View itemView) {super(itemView);planEt = itemView.findViewById(R.id.plan_edit);deleteBtn = itemView.findViewById(R.id.delete_btn);}}@NonNull@Overridepublic ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {View view = LayoutInflater.from(mContext).inflate(mLayout,viewGroup,false);return new ViewHolder(view);}@Overridepublic void onBindViewHolder(@NonNull final ViewHolder viewHolder, int i) {Plan plan = mPlanList.get(i);final EditText mEt = viewHolder.planEt;final Button mBtn = viewHolder.deleteBtn;final int position = viewHolder.getAdapterPosition();//设置文本mEt.setText(plan.content);//绑定文本更新的监听器final Watcher mWatcher = new Watcher(position);//仅当文本框获得焦点时才添加文本更新的监听器,失去焦点时删除该监听器mEt.setOnFocusChangeListener(new View.OnFocusChangeListener() {@Overridepublic void onFocusChange(View view, boolean b) {if(b) {mEt.addTextChangedListener(mWatcher);} else  {mEt.removeTextChangedListener(mWatcher);}}});//绑定删除按钮的点击事件的监听器mBtn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {//设置禁止再次点击,否则因某些原因连续点击程序将闪退mBtn.setClickable(false);//回调点击的按钮在列表中的位置onDeleteBtnClickListener.onDeleteBtnClick(position);}});}@Overridepublic int getItemCount() {return mPlanList.size();}/*** 设置删除按钮监听器* @param onDeleteBtnClickListener  自定义监听器*/public void setOnDeleteBtnClickListener(onDeleteBtnClickListener onDeleteBtnClickListener){this.onDeleteBtnClickListener = onDeleteBtnClickListener;}/*** 设置文本更新监听器* @param onTextChangedListener  自定义监听器*/public void setOnTextChangedListener(onTextChangedListener onTextChangedListener){this.onTextChangedListener = onTextChangedListener;}/*** 删除按钮点击事件接口*/public interface onDeleteBtnClickListener{void onDeleteBtnClick(int position);}/*** 文本更新事件接口*/public interface onTextChangedListener{void onTextChanged(int position,Editable text);}/***  自定义TextWatcher*/class Watcher implements TextWatcher {private int position;Watcher(int position){this.position = position;}@Overridepublic void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}@Overridepublic void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}@Overridepublic void afterTextChanged(Editable editable) {//在文本框内容更新后,回调对应文本框的位置和内容onTextChangedListener.onTextChanged(position,editable);}};
}

布局非常简单,一个输入框和一个按钮

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="5dp"><EditTextandroid:id="@+id/plan_edit"android:layout_width="220dp"android:layout_height="28dp"android:paddingTop="4dp"android:paddingBottom="4dp"android:paddingStart="6dp"android:paddingEnd="6dp"android:layout_alignParentStart="true"android:background="@drawable/selector_edit_plan"android:textSize="16sp"android:maxLength="12"android:hint="制定一个计划吧"/><Buttonandroid:id="@+id/delete_btn"android:layout_width="28dp"android:layout_height="28dp"android:layout_alignParentEnd="true"android:background="@drawable/bg_btn_delete"android:textSize="20sp"android:text="×"android:textColor="@color/white"/></RelativeLayout>

3.完整的计划页面
在计划页面中定义并使用对话框,编写数据库操作的代码,得到完整的页面。此处需要注意的是:

  • 之前说过的列表数据变化后,适配器通知更新的问题
  • 这里仅展示了DailyPlanSettingAdapter,即设置日常计划用的适配器,另外使用的DailyPlanDisplayAdapter未展示,但实际相差不大,甚至更简单
  • 在此使用了一种比较笨的方法更新数据,当点击修改按钮时,深度拷贝一份当前的计划列表到临时列表中,即这段代码 temporaryList = Utils.deepCopy(dailyPlanList);
    这样一来,dailyPlanList中保存了原有的计划数据,temporaryList中保存了变化后的计划数据。保存时,将temporaryList中的数据保存进数据库(控制主键的值以达到覆盖效果),如果发现temporaryList的长度小于dailyPlanList的长度,就删除数据库中多余的数据。
    之所以要使用深度拷贝,是因为如果使用浅拷贝的话,两个列表仍指向同一个地址,即改变一个列表中内容的同时也会改变另一个列表,这样达不到需要的效果

参考博客(深度拷贝)
https://www.iteye.com/blog/bijian1013-2358367

public class PlanFragment extends Fragment implements View.OnClickListener{private ImageView dailyPlanIcon;    //装饰图标private TextView dailyPlanTv;       //修改日常计划的文本按钮private RecyclerView dailyPlanRv;   //显示每日计划的列表private List<Plan> dailyPlanList = new ArrayList<>();   //保存Plan数据的列表private List<Plan> temporaryList = new ArrayList<>();   //用于保存修改中的Plan,最终用于更新数据库private DailyPlanDisplayAdapter displayAdapter;         //展示每日计划的列表的适配器private final static int MAX_DAILY_PLAN_NUM = 7;        //每日计划数量最大值@Nullable@Overridepublic View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {final View view = inflater.inflate(R.layout.fragment_plan,container,false);/* 获取组件,绑定图片资源 */dailyPlanIcon = view.findViewById(R.id.daily_plan_img);dailyPlanTv = view.findViewById(R.id.daily_modify_tv);dailyPlanRv = view.findViewById(R.id.daily_plan_rv);dailyPlanIcon.setImageResource(R.drawable.ic_plan1);/* 从数据库获取Plan数据 */updatePlanList();/* 显示DailyPlan列表 */displayAdapter = new DailyPlanDisplayAdapter(getContext(),R.layout.item_plan_display,dailyPlanList,0);LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());layoutManager.setOrientation(LinearLayoutManager.VERTICAL);dailyPlanRv.setLayoutManager(layoutManager);dailyPlanRv.setAdapter(displayAdapter);/* 绑定点击事件 */dailyPlanTv.setOnClickListener(this);return view;}@Overridepublic void onClick(View view) {switch (view.getId()){case R.id.daily_modify_tv://使用临时列表展示数据,并保存用户的修改后的数据(深度拷贝)temporaryList.clear();try {temporaryList = Utils.deepCopy(dailyPlanList);} catch (Exception e) {e.printStackTrace();}//显示修改计划的对话框showDialog();break;default:break;}}private void showDialog(){//实例化自定义对话框final PlanDialog dailyPlanDialog = new PlanDialog(Objects.requireNonNull(getActivity()));//设置对话框标题dailyPlanDialog.setTitle("每日计划");//设置背景图片dailyPlanDialog.setBackground(R.drawable.pic_bg_dialog);//设置列表适配器final DailyPlanSettingAdapter settingAdapter = new DailyPlanSettingAdapter(getActivity(),R.layout.item_plan_setting,temporaryList);/* 设置自定义监听器 *///计划文本更新的监听器settingAdapter.setOnTextChangedListener(new DailyPlanSettingAdapter.onTextChangedListener() {@Overridepublic void onTextChanged(int position, Editable text) {//System.out.println("position is " + position + "  text is " + text);Plan plan = temporaryList.get(position);plan.content = text.toString();temporaryList.set(position,plan);}});//删除按钮点击事件监听器settingAdapter.setOnDeleteBtnClickListener(new DailyPlanSettingAdapter.onDeleteBtnClickListener() {@Overridepublic void onDeleteBtnClick(int position) {temporaryList.remove(position);//通知Dialog中的适配器更新内容dailyPlanDialog.notifyRemoved(position);while (position < temporaryList.size()){dailyPlanDialog.notifyChanged(position);position++;}}});dailyPlanDialog.setAdapter(settingAdapter);//设置按钮的文本及监听事件//保存按钮dailyPlanDialog.setOnConfirmBtnClickListener("保存",new PlanDialog.onConfirmBtnClickListener() {@Overridepublic void onConfirmBtnClick() {int position = 0;for (Plan plan : temporaryList){plan.isDaily = true;plan.id = position;plan.save();position++;System.out.println(plan);}//如果修改后列表的长度小于原列表,则删除数据库多余的数据int x = dailyPlanList.size() - temporaryList.size();while (x > 0){dailyPlanList.get(dailyPlanList.size() - x).delete();x--;}updatePlanList();displayAdapter.notifyDataSetChanged();dailyPlanDialog.dismiss();}});//取消按钮dailyPlanDialog.setOnCancelBtnClickListener("取消", new PlanDialog.onCancelBtnClickListener() {@Overridepublic void onCancelBtnClick() {dailyPlanDialog.dismiss();}});//添加按钮dailyPlanDialog.setOnModifyTextClickListener("添加",new PlanDialog.onModifyTextClickListener() {@Overridepublic void onModifyTextClick() {if(temporaryList.size() < MAX_DAILY_PLAN_NUM){temporaryList.add(new Plan());//通知Dialog中的适配器更新内容dailyPlanDialog.notifyInserted(temporaryList.size() - 1);} else {Toast.makeText(getActivity(),"每日计划不宜过多哦~",Toast.LENGTH_SHORT).show();}}});dailyPlanDialog.show();}/*** 更新Plan列表*/private void updatePlanList(){dailyPlanList.clear();dailyPlanList.addAll(SQLite.select().from(Plan.class).where(Plan_Table.isDaily.is(true)).queryList());}
}

计划页面的布局文件

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/whiteSmoke"><android.support.design.widget.AppBarLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><android.support.v7.widget.Toolbarandroid:id="@+id/toolbar"android:layout_width="match_parent"android:layout_height="?attr/actionBarSize"app:layout_scrollFlags="scroll|enterAlways"android:background="@drawable/action_bar_bg"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:textColor="@color/white"android:textStyle="bold"android:textSize="18sp"android:text="计划"/></android.support.v7.widget.Toolbar></android.support.design.widget.AppBarLayout><android.support.v4.widget.NestedScrollViewandroid:layout_width="match_parent"android:layout_height="wrap_content"app:layout_behavior="@string/appbar_scrolling_view_behavior"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:padding="8dp"android:background="@color/white"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><ImageViewandroid:id="@+id/daily_plan_img"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentStart="true"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_toEndOf="@+id/daily_plan_img"android:layout_centerVertical="true"android:layout_marginStart="4dp"android:text="每日计划"/><TextViewandroid:id="@+id/daily_modify_tv"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentEnd="true"android:layout_centerVertical="true"android:text="修改"/></RelativeLayout><android.support.v7.widget.RecyclerViewandroid:id="@+id/daily_plan_rv"android:layout_width="match_parent"android:layout_height="wrap_content"></android.support.v7.widget.RecyclerView></LinearLayout></android.support.v4.widget.NestedScrollView></android.support.design.widget.CoordinatorLayout>

安卓开发日记——MyDiary(1)相关推荐

  1. 安卓开发日记(1) - 安装 Android 开发环境和 first app

    安装 Android 开发环境 安装 Android Studio 并升级到最新版本(本文使用 1.0.2 版本) 如果没有安装JDK, 安装 64-bit JDK MAC 下需要手动装一下HAXAM ...

  2. 安卓开发学习日记第三天_新手怪button_莫韵乐的欢乐笔记

    安卓开发学习日记第三天--新手怪button (不是buttercup,虽然里面好像也有button,心中已经响起那段音乐了) 前情提要: 第一天学习日记之安装Android Studio3.6 第二 ...

  3. 安卓开发学习日记第四天_会爬就会跑_莫韵乐的欢乐笔记

    安卓开发学习日记第四天_会爬就会跑 前情提要 安卓开发学习日记第一天Android Studio3.6安装 安卓开发学习日记第二天_破坏陷阱卡之sync的坑 安卓开发学习日记第三天_新手怪button ...

  4. 安卓开发学习日记第二天_破坏陷阱卡之sync的坑_莫韵乐的快乐笔记

    安卓开发学习日记第二天 前情提要:第一天的日记 经过第一天的日记,我们顺利第安装上了Android Studio3.6 第二天内容包括: 1.推荐的文件目录结构 2.如何创建一个项目 3.遇到sync ...

  5. 安卓开发学习日记第一天(笑)_Android Studio3.6安装_莫韵乐的快乐笔记

    安卓开发学习第一天 Android Studio3.6安装 没想到终于要写出自己的第一个博客了 反正是第一篇,有没有人看都无所谓(理智:129/129) 万事开头难,做下去就容易很多了 言归正传 工欲 ...

  6. 安卓开发学习日记第四天番外篇_用Kotlin炒冷饭——越炒越小_莫韵乐的欢乐笔记

    安卓开发学习日记第四天番外篇--用Kotlin炒冷饭--越炒越小 前情提要 安卓开发学习日记第一天_Android Studio3.6安装 安卓开发学习日记第二天_破坏陷阱卡之sync的坑 安卓开发学 ...

  7. 安卓开发学习日记第五天——奇怪的bug出现了(VT-x说没就没)_莫韵乐的欢乐日记

    安卓开发学习日记第五天--奇怪的bug出现了(VT-x说没就没) 前情提要: 安卓开发学习日记第一天_Android Studio3.6安装 安卓开发学习日记第二天_破坏陷阱卡之sync的坑 安卓开发 ...

  8. Android安卓开发中图片缩放讲解

    安卓开发中应用到图片的处理时候,我们通常会怎么缩放操作呢,来看下面的两种做法: 方法1:按固定比例进行缩放 在开发一些软件,如新闻客户端,很多时候要显示图片的缩略图,由于手机屏幕限制,一般情况下,我们 ...

  9. 如果成为一名高级安卓开发_什么是高级开发人员,我如何成为一名开发人员?

    如果成为一名高级安卓开发 Becoming a Senior Developer is something many of us strive for as we continue our code ...

最新文章

  1. platform下的js分析_1
  2. TensorFlow——[基本图像分类]fashion-mnist及mnist_reader.py运行错误[TypeError: Invalid dimensions for image data]
  3. 从 0 到 1 实现浏览器端沙盒运行环境
  4. SQL强化(二) 在Oracle 中写代码
  5. 我到底该信谁?另外,我发现了抢火车票的秘密
  6. 一个mysql复制中断的案例
  7. python读取指定行到最后一行_python读取文件最后一行两种方法
  8. tail,more查看日志(定点和翻页)
  9. Gnosis发起提案就是否推出Gnosis协议v2版本展开讨论
  10. java final对象_java面向对象基础_final详细介绍
  11. sharepoint获取当前网址
  12. 神经网络与深度学习第4章:前馈神经网络 阅读提问
  13. android开发下载文件损坏解决方法,Android中已下载的.apk文件已损坏
  14. 大唐双龙传JAVA版小游戏_大唐双龙传_JAVA游戏免费版下载_7723手机游戏[www.7723.cn]...
  15. mysql分级建表_Mysql如何使用命令实现分级查找帮助详解
  16. 再掀融资潮 团购网仍后劲不足(团购现状分析)
  17. 绝版 ~ 原来 Python 画画可以这么简单
  18. Alarm机制-学习记录
  19. android:state_pressed是什么意思?
  20. 树和二叉树(TreeBinary Tree)

热门文章

  1. linux erp 流程管理软件,TIPTOP ERP 之 AZZ整体系统管理
  2. 全网爬取6500多只基金|看看哪家基金最强
  3. 机械设计软件CAD系统—文件格式基础知识
  4. 利用快捷方式、批处理快速登录QQ
  5. 线性相关系数、卡方检验、互信息
  6. 亲爱的爸爸妈妈,你们一定要好好的呀
  7. The Golem Group/University of California at Los Angeles Autonomous Ground Vehicle in the DARPA Grand
  8. 线段树合并(HDU-5575 Discover Water Tank)
  9. laravel-admin微信网页授权获取用户信息过程
  10. 头条面试官:5 亿整数的大文件,如何排序 ?