RecyclerView重点feature记录

本文深入探讨了RecyclerView的高级特性,包括多布局管理、头部与底部添加、点击事件处理、局部更新、自定义分割线、动画效果、滑动删除与拖拽等功能的实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一些非常基础的feature这里就不记录了,主要记录一些重难点。

 

看一下这次我们的成品: 



 

主要几个feature如下:


  • 多布局

  •  添加头部和底部

  •  添加点击事件,通过多种方式实现

  •  item的局部更新

  •  添加的定制的分割线

  •  item的动画

  •  滑动删除item和长按拖拽

  • 其他

 

我们一个一个来说。

 

    1.多布局


  • 实现:

 

有时候为了实现特定的效果,需要recycler view可以有多种布局格式,我们需要用ViewType进行区分。

 

RecyclerView提供了方法去区分ViewType

 

@Override

public int getItemViewType(int position) {

    return super.getItemViewType(position);

}

 

onCreateViewHolder的第二个参数int viewType就是getItemViewType的返回值。

 

@Override

public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    return null;

}

 

所以我们只需要为不同的布局制定不同的viewholder,并

 

onbindviewholder中根据不同的holder类型;

oncreateviewholder中根据不同的viewtype

getitemviewtype中根据不同的position

 

而定制并选择不同的布局即可。

代码奉上:

@Override

public int getItemViewType(int position) {

    if (position % 2 == 0) {

        return TWO_ITEM;

    } else {

        return ONE_ITEM;

    }

}

class OneViewHolder extends RecyclerView.ViewHolder {

    TextView tv1;

 

    public OneViewHolder(View itemView) {

        super(itemView);

        tv1 = (TextView) itemView.findViewById(R.id.text_recycler_view);

    }

}

class TwoViewHolder extends RecyclerView.ViewHolder {

    TextView tv21, tv22;

     public TwoViewHolder(View itemView) {

        super(itemView);

        tv21 = (TextView) itemView.findViewById(R.id.text_recycler_view21);

        tv22 = (TextView) itemView.findViewById(R.id.text_recycler_view22);

    }

}

@Override

public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    LayoutInflater mInflater = LayoutInflater.from(context);

    RecyclerView.ViewHolder holder = null;

    if (ONE_ITEM == viewType) {

        View v = mInflater.inflate(R.layout.item_viewtype1, parent, false);

        holder = new OneViewHolder(v);

    } else {

        View v = mInflater.inflate(R.layout.item_viewtype2, parent, false);

        holder = new TwoViewHolder(v);

    }

    return holder;

}

@Override

public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

    if (holder instanceof OneViewHolder) {

        ((OneViewHolder) holder).tv1.setText("布局1" + position);

    } else {

        ((TwoViewHolder) holder).tv21.setText("布局2" + position);

        ((TwoViewHolder) holder).tv22.setText("布局2" + position);

    }

}

 

  •  遇到的问题:无。

  •  解决:无。

 


     2.添加头部和底部


  • 实现:

 

思路:用多布局的方式实现,由于在recyclerview中添加item和添加首部尾部是不一样的,所以我们可以在原有的recyclerview的适配器基础上封装一个首部尾部适配器,根据viewtype的值确定返回的类型是首部尾部还是recyclerview本身。

 

这么说可能不太容易理解,我们还是通过代码来理解:

 

1.首先创建新的适配器HeaderAndFooterAdapter

 

2.创建两个可以存入首部尾部view的泛型变量 mHeaderViewmFooterView

protected SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>();

protected SparseArrayCompat<View> mFooterViews = new SparseArrayCompat<>();

 

3.其次我们需要创建这几个方法:

public void addHeaderView(View view) {

    mHeaderViews.put(BASE_ITEM_TYPE_HEADER++, view);

    int index = mHeaderViews.indexOfValue(view);

    notifyItemInserted(index);

}

public void addFooterView(View view) {

    mFooterViews.put(BASE_ITEM_TYPE_FOOTER++, view);

    int index = mFooterViews.indexOfValue(view) + getHeadersCount() + getRealItemCount();

    notifyItemInserted(index);

}

public void removeHeaderView(View view) {

    int index = mHeaderViews.indexOfValue(view);

    if (index < 0) return;

    mHeaderViews.removeAt(index);

    notifyItemRemoved(index);

}

public void removeFooterView(View view) {

    int index = mFooterViews.indexOfValue(view);

    if (index < 0) return;

    mFooterViews.removeAt(index);

    index = index + getHeadersCount() + getRealItemCount();

    notifyItemRemoved(index);

}


这些方法需要由adapter来调用并将创建的headerviewfoorterview传入,放入泛型中,找到此view在泛型中的位置,并用notifyItemInserted方法来插入到recyclerview中,移除的方法也是类似,还是很好理解的。

 

4.接下来就是重写RecyclerViewAdater的方法了,同样我们需要在

 

onCreateViewHolder中根据viewtype来判断需要传入给RecyclerView.ViewHolderView

 

onBindViewHolder中根据position判断是首部尾部还是RecyclerView本身的Item,如果是本身的Item,则调用原本的adapter

 

getItemViewType中根据position判断返回的int值;

 

代码奉上:

@Override

public int getItemCount() {

    return getRealItemCount() + getHeadersCount() + getFootersCount();

}

@Override

public int getItemViewType(int position) {

    if (isHeaderPosition(position)) {

        return mHeaderViews.keyAt(position);

    }

    if (isFooterPosition(position)) {

        return mFooterViews.keyAt(position - getHeadersCount() - getRealItemCount());

    }

    return mRealAdapter.getItemViewType(position - getHeadersCount());

}

@Override

public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    if (isHeaderType(viewType)) {

        int headerPosition = mHeaderViews.indexOfKey(viewType);

        View headerView = mHeaderViews.valueAt(headerPosition);

        return new RecyclerView.ViewHolder(headerView) {

        };

    }

    if (isFooterType(viewType)) {

        int footerPosition = mFooterViews.indexOfKey(viewType);

        View footerView = mFooterViews.valueAt(footerPosition);

        return new RecyclerView.ViewHolder(footerView) {

        };

    }

    return mRealAdapter.onCreateViewHolder(parent, viewType);

}

@Override

public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

    if (isHeaderPosition(position) || isFooterPosition(position)) {

    } else {

        final int realPosition = position - getHeadersCount();

        mRealAdapter.onBindViewHolder(holder, realPosition);

    }

}

  •  遇到的问题:无。

  •  解决:无。

     3.添加点击事件,通过多种方式实现


  • 实现:

 

首先第一种实现方式一定是在adapter中通过(以点击为例)

 

1.创建点击接口OnItemClickListener,并且创建抽象手势方法OnItemClick()

2.在需要回调抽象方法的类中传入此类的实例setOnItemClickListener(this)

3.holder.itemView监听事件的重写中通过传进来的实例调用抽象方法mOnItemClickListener.OnItemClick(position)

4.在回调中实现OnItemClick()方法。

 

可以在原adapterHeaderAndFooterAdapter中捕捉手势。

 

第二种方式是重写RecyclerViewOnItemTouchListener,并通过GestureDetector类捕捉手势,其他步骤和第一种格式类似,这里不再赘述。

 

第二种方式:


GestureDetector mGestureDetector;

private View childView;

private RecyclerView touchView;



public interface OnItemClickListener {

    void onItemClick(View view, int position);

    void onLongClick(View view, int posotion);

}

public RecyclerItemClickListener(Context context, final RecyclerItemClickListener.OnItemClickListener mListener) {

    mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {

        @Override

        public boolean onSingleTapUp(MotionEvent ev) {

            if (childView != null && mListener != null) {

                mListener.onItemClick(childView, touchView.getChildPosition(childView));

            }

            return true;

        }

        @Override

        public void onLongPress(MotionEvent ev) {

            if (childView != null && mListener != null) {

                mListener.onLongClick(childView, touchView.getChildPosition(childView));

            }

        }

    });

}

@Override

public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {

    mGestureDetector.onTouchEvent(e);

    childView = rv.findChildViewUnder(e.getX(), e.getY());

    touchView = rv;

    return false;

}

  •  遇到的问题:无。

  •  解决:无。

 

     4.item的添加删除与局部更新


  • 实现:

 

1.RecyclerviewItem的添加删除

 

RecyclerViewAdpater里面相比较ListViewAdapter,主要多了这几个方法

 

  1. notifyItemChanged(int position) 更新列表position位置上的数据可以调用
  2. notifyItemInserted(int position) 列表position位置添加一条数据时可以调用,伴有动画效果
  3. notifyItemRemoved(int position) 列表position位置移除一条数据时调用,伴有动画效果
  4.  notifyItemMoved(int fromPosition, int toPosition) 列表fromPosition位置的数据移到toPosition位置时调用,伴有动画效果
  5.  notifyItemRangeChanged(int positionStart, int itemCount) 列表从positionStart位置到itemCount数量的列表项进行数据刷新
  6.  notifyItemRangeInserted(int positionStart, int itemCount) 列表从positionStart位置到itemCount数量的列表项批量添加数据时调用,伴有动画效果
  7.  notifyItemRangeRemoved(int positionStart, int itemCount) 列表从positionStart位置到itemCount数量的列表项批量删除数据时调用,伴有动画效果

 

那么我们item的添加和删除也就出来了:

public void addData(int position, String urlStr) {

    url.add(position, urlStr);

    notifyItemInserted(position);

}

public void removeData(int position) {

    if (position < url.size()) {

        url.remove(position);

        notifyItemRemoved(position);

    }

}

  •  遇到的问题:然而我们发现添加了新的item以后itemposition发生了错位,查询资料后得知这两个方法仅仅是起到界面动画的效果,实际上并没有进行数据与界面的重新绑定,所以我们需要在后面加上:

notifyItemRangeChanged(position,itemCount);


  • 解决:

 

其实通过观察方法名字可以看出来,无论是notifyItemInserted还是notifyItemRemoved的方法名中,都没有“Changed”这个单词。所有notify开头的方法中,仅仅只有以下三个方法带有“Changed”单词且具有重新绑定数据与界面的功能:

 

notifyDataSetChanged();//通知重新绑定所有数据与界面  

notifyItemChanged(int);//通知重新绑定某一个Item的数据与界面  

notifyItemRangeChanged(intint);//通知重新绑定某一范围内的的数据与界面 

 

2.Item的局部更新

 

这里的局部更新并不是指对一个item的更新,而是一个item中某一个控件的局部更新,我们知道在RecyclerViewAdapter中有方法notifyItemChangedposition),作用是更新position对应的Item

 

代码奉上:

 

public void updateDate(int position, String textStr) {

    if (position < textList.size()) {

        textList.set(position, textStr);

        notifyItemChanged(position);

    }

}

 

  • 遇到的问题:在RecyclerViewLayoutManagerGridLayoutManager时更新布局中的TextView控件,布局中的ImageView控件闪烁。

  • 解决:

 

public void addData(int position, String urlStr) {

    url.add(position, urlStr);

    notifyItemInserted(position);//通知演示插入动画  

    notifyItemRangeChanged(position, url.size() - position);//通知数据与界面重新绑定

}

 

public void removeData(int position) {

    if (position < url.size()) {

        url.remove(position);

        notifyItemRemoved(position);//通知演示插入动画  

        notifyItemRangeChanged(0, url.size() - position);//通知数据与界面重新绑定

    }

}

在查阅资料后发现,图片闪烁可能是 


然而通过将iamgeview固定宽高,取消recyclerviewitem animator动画,或是对网络库的动画进行限制等,都没有解决问题。

 

最终在notifyItemChangedAPI中,我们发现多了参数payloadnotifyItemChanged方法在对参数payload的描述中有这样一句:

 

payload Optional parameter, use null to identify a "full" update

 

/**

 * Notify any registered observers that the item at <code>position</code> has changed with

 * an optional payload object.

 *(省略)

 * @param position Position of the item that has changed

 * @param payload Optional parameter, use null to identify a "full" update

 *

 * @see #notifyItemRangeChanged(int, int)

 */

public final void notifyItemChanged(int position, Object payload) {

    mObservable.notifyItemRangeChanged(position, 1, payload);

}

也就是说如果payload参数是null,那么就会来一个完整的更新,也就是说会全部更新。

 

我们再看一下mRecyclerViewAdapter.notifyItemChanged(position)的源码:

/**

 * Notify any registered observers that the item at <code>position</code> has changed with

 * an optional payload object.

 *(省略)

 * @param position Position of the item that has changed

 * @param payload Optional parameter, use null to identify a "full" update

 *

 * @see #notifyItemRangeChanged(int, int)

 */

public final void notifyItemChanged(int position, Object payload) {

    mObservable.notifyItemRangeChanged(position, 1, payload);

}

从源码中看到,notifyItemChanged(position)调用了 notifyItemRangeChanged(int positnStart, int itemCount)方法,源码如下:
public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {

    // since onItemRangeChanged() is implemented by the app, it could do anything, including

    // removing itself from {@link mObservers} - and that could cause problems if

    // an iterator is used on the ArrayList {@link mObservers}.

    // to avoid such problems, just march thru the list in the reverse order.

    for (int i = mObservers.size() - 1; i >= 0; i--) {

        mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);

    }

}

 

notifyItemRangeChanged(int positionStart, int itemCount)方法最终还是调用了notifyItemRangeChanged(int positionStart, int itemCount, Object payload)方法,只是payload参数是null

 

那么如果payload传一个不为null的参数,就可以实现对列表项中的具体控件更新了吗?我们通过代码验证下。

 

这里我们payload复制为一个无意义的字符串“fugui”,然后重写带有List<Object> payloads参数的onBindViewHolder

 

@Override

public void onBindViewHolder(TextViewHolder holder, int position, List<Object> payloads) {

    if (payloads.isEmpty()) {

        onBindViewHolder(holder, position);

    } else {

        holder.textView.setText(title);

    }

}


如果payload不为空,那么我们更新我们Item中的控件textview(注意不更新的控件不要写在此处),测试后发现,果然图片不再闪烁了!

    5.添加的定制的分割线

 

  • 实现:

 

我们知道在recyclerview中有addItemDecoration方法,我们的需求是可定制分割线的颜色,高度,Item顶部和底部是否有分割线等。

 

1.首先,创建CustomerDecoration继承自RecyclerView.ItemDecoration,除了

 

divider在我们传入的颜色基础上由paint画笔重新绘制;

还有顶部底部是否显示的top参数需要更改;

 

以外,其他部分和官方提供的DividerItemDecoration很相似。我们需要重写的方法有

 

onDraw()                 (负责绘制)

getItemOffets()       (负责得到分割线的尺寸)

 

2.CustomerDecoration的构造方法中传入定制的参数(上下文,方向,高度,颜色,是否显示最顶部分割线,是否显示自底部分割线)。

 

recyclerView.addItemDecoration(new CustomerDecoration(this

        , LinearLayoutManager.HORIZONTAL, 5, R.color.gray, false, false));

 

并在构造方法中根据颜色初始化画笔Paint

 

public CustomerDecoration(Context context, int orientation, int dividerHeight

        , int dividerColor, boolean up, boolean down) {

    this(context, orientation);

    this.mContext = context;

    mDividerHeight = dividerHeight;

    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    mPaint.setColor(dividerColor);

    mPaint.setStyle(Paint.Style.FILL);

    this.up = up;

    this.down = down;

}


3.然后重写getItemOffsets方法(根据传入的分割线高度mDividerHeight设置分割线矩形):

//获取分割线尺寸

@Override

public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {

    super.getItemOffsets(outRect, view, parent, state);

    outRect.set(0, 0, 0, mDividerHeight);

}

 

4.然后是onDraw方法:

 

@Override

public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {

    super.onDraw(c, parent, state);

    if (mOrientation == LinearLayoutManager.VERTICAL) {

        drawVertical(c, parent);

    } else {

        drawHorizontal(c, parent);

    }

}


这里比较重要的就是其中的drawVerticaldrawHorizontal方法,我们拿drawHorizontal来讲解。

 

private void drawHorizontal(Canvas canvas, RecyclerView parent) {

    final int left = parent.getPaddingLeft();

    final int right = parent.getMeasuredWidth() - parent.getPaddingRight();

    final int childSize = parent.getChildCount();

    int top = 0,bottom;

    View child = null;

    RecyclerView.LayoutParams layoutParams = null;

    for (int i = 0; i < childSize; i++) {

        child = parent.getChildAt(i);

        layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();

        top = child.getBottom() + layoutParams.bottomMargin;

        bottom = top + mDividerHeight;

        if (mDivider != null) {

            mDivider.setBounds(left, top, right, bottom);

            mDivider.draw(canvas);

        }

        if (mPaint != null) {

            canvas.drawRect(left, top, right, bottom, mPaint);

        }

    }

}

首先通过onDraw中的第二个参数Recyclerview的实例获得recyclerview距左右的内边距,也就是分割线的左右位置,及Item的个数,接下来在for 循环中通过获取每个Item底部长度Item的底部边距来确定分割线的顶部位置,然后加上分割线高度得到分割线底部位置。

 

纵向绘制道理也是一样的。

 

通过这样的绘制流程,我们能够绘制出每个item底部的分割线,那么最顶部的如何绘制呢。

答案很简单,只要在此方法中判断绘制顶部的参数是否为true,若为true,则修改顶部值为:

 

top = child.getTop() - layoutParams.bottomMargin;

 

 

top = child.getBottom() - layoutParams.bottomMargin - child.getHeight();

即可。

 

由于最底部本来就已经绘制,所以如果想去掉底部,则在for循环之前添加:

 

if (!down) {

    childSize = childSize - 1;

}

则若将参数down设置为false传入,则底部分割线就不会显示啦!

  •  遇到的问题:无。

  •  解决:无。

 

     6.item的动画


  • 实现:

 

DefaultItemAnimatorAndroid OS中一个默认的RecyclerView动画实现类,如果产品需求没有特别复杂的动画要求,可以使用DefaultItemAnimator实现简单的动画效果。

animator = new DefaultItemAnimator();

animator.setAddDuration(500);

animator.setRemoveDuration(500);

animator.setChangeDuration(500);

animator.setMoveDuration(500);

 
recyclerView.setItemAnimator(animator);

 

这样,简单的动画效果就实现了。

  •  遇到的问题:当给position0的位置添加item时,由于不能自动滑动到新添加的item位置,所以这里的item动画效果我们是看不见的。

  • 解决:

 

在添加item之前,加上:

  

recyclerView.scrollToPosition(position);

就能够在动画显示前滑动到相应位置了。

 

     7.滑动删除item和长按拖拽

  • 实现:

 

ItemTouchHelper support-v7包中加入的一个帮助开发人员处理拖拽和滑动的实现类,它能够让你非常容易实现侧滑删除、拖拽的功能。

我们只需要实例化一个ItemTouchHelper,然后关联到RecyclerViewOK了:

 

 

ItemTouchHelper itemTouchHelper = new ItemTouchHelper(mCallback);

itemTouchHelper.attachToRecyclerView(recyclerView);

 

ItemTouchHelper的实现:

ItemTouchHelper.SimpleCallback mCallback = new ItemTouchHelper.SimpleCallback(

        ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT

                | ItemTouchHelper.RIGHT, ItemTouchHelper.START | ItemTouchHelper.END) {

    @Override

    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {

        int fromPosition = viewHolder.getAdapterPosition();

        int toPosition = target.getAdapterPosition();

        textAdapter.moveDate(fromPosition, toPosition);

        return true;

    }

    @Override

    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {

        int position = viewHolder.getAdapterPosition();

        textAdapter.removeData(position);

    }

};
 

public void moveDate(int i, int j) {

    String t = url.get(i);

    url.set(i, url.get(j));

    url.set(j, t);

    notifyItemMoved(i, j);

}

public void removeData(int position) {

    if (position < url.size()) {

        url.remove(position);

        notifyItemRemoved(position);

        notifyItemRangeChanged(0,url.size()-position);

    }

}

  •  遇到的问题:无。

  •  解决:无。

 

    8.其他


  • 效果: 



  • 实现:

 

下拉刷新用了android.support.v4.widget包中的SwipeRefreshLayout,用法比较简单:

 

<android.support.v4.widget.SwipeRefreshLayout

    android:id="@+id/swipeRefreshLayout"

    android:layout_width="match_parent"

    android:layout_height="match_parent">

 

    <android.support.v7.widget.RecyclerView

        android:layout_width="match_parent"

        android:layout_height="match_parent"/>

 

</android.support.v4.widget.SwipeRefreshLayout>

 

@BindView(R.id.swipeRefreshLayout)

SwipeRefreshLayout refreshLayout;

 

ButterKnife.bind(this);

 

refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {

    @Override

    public void onRefresh() {

        textAdapter.updateAllData(App.urlNew);

        refreshLayout.setRefreshing(false);

 

    }

});

 

网络库用了Universal-Image-Loader

 

ImageLoader.getInstance().displayImage(url.get(position), holder.imageView, mOptions, new SimpleImageLoadingListener() {

    @Override

    public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {

        super.onLoadingComplete(imageUri, view, loadedImage);

        holder.imageView.setImageBitmap(loadedImage);

    }

});

 

  •  遇到的问题:无。

  •  解决:无。

 

到这里重点feature就总结完成了!


完整代码地址:http://download.youkuaiyun.com/download/denglixuan1996/10265122

AndroidManifest.xml:<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.diaryapp"> <uses-feature android:name="android.hardware.camera" android:required="false" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.CAMERA" /> <application android:allowBackup="true" android:hardwareAccelerated="true" android:largeHeap="true" android:requestLegacyExternalStorage="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.HelloWorld"> <activity android:name=".MainActivity" android:label="@string/title_activity_main" android:exported="false" /> <activity android:name=".DiaryActivity" android:label="@string/title_activity_diary" android:exported="false" /> <activity android:name=".RegisterActivity" android:label="@string/title_activity_register" android:exported="false" /> <activity android:name=".LoginActivity" android:label="@string/title_activity_login" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> DiaryAdapter: package com.example.diaryapp.adapters; import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.example.diaryapp.utils.GlideEngine; import com.example.diaryapp.DiaryActivity; import com.example.diaryapp.R; import com.example.diaryapp.models.Diary; import java.util.List; public class DiaryAdapter extends RecyclerView.Adapter<DiaryAdapter.DiaryViewHolder> { private Context context; private List<Diary> diaryList; private int userId; public DiaryAdapter(Context context,List<Diary> diaryList,int userId){ this.context=context; this.diaryList=diaryList; this.userId=userId; // 通过构造函数传递 userId } // 当数据更新时调用此方法 @SuppressLint("NotifyDataSetChanged") public void updateDiaries(List<Diary> newDiaries){ this.diaryList.clear(); this.diaryList.addAll(newDiaries); notifyDataSetChanged(); } @NonNull @Override public DiaryAdapter.DiaryViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view= LayoutInflater.from(context).inflate(R.layout.item_diary,parent,false); return new DiaryViewHolder(view); } // 绑定视图持有者方法 @Override public void onBindViewHolder(@NonNull DiaryAdapter.DiaryViewHolder holder, int position) { Diary diary=diaryList.get(position); holder.tvTitle.setText(diary.getTitle()); holder.tvDate.setText(diary.getDate()); holder.tvContent.setText(diary.getContent()); holder.tvWordCount.setText("字数:" + diary.getContent().length()); if (diary.getImagePath() != null && !diary.getImagePath().isEmpty()) { GlideEngine engine = GlideEngine.createGlideEngine(); engine.loadImage(context,diary.getImagePath(),holder.ivImage); } else { holder.ivImage.setBackgroundColor(context.getResources().getColor(android.R.color.darker_gray)); } holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent=new Intent(context, DiaryActivity.class); intent.putExtra("diaryId",diary.getId()); intent.putExtra("userId",userId); // 确保传递正确的 userId context.startActivities(new Intent[]{intent}); } }); } @Override public int getItemCount() { return diaryList.size(); } public static class DiaryViewHolder extends RecyclerView.ViewHolder { private TextView tvTitle,tvDate,tvContent,tvWordCount; private ImageView ivImage; public DiaryViewHolder(@NonNull View itemView) { super(itemView); tvTitle=itemView.findViewById(R.id.tvTitle); tvDate=itemView.findViewById(R.id.tvDate); tvContent=itemView.findViewById(R.id.tvContent); ivImage=itemView.findViewById(R.id.ivImage); tvWordCount=itemView.findViewById(R.id.tvWordCount); } } } Diary:package com.example.diaryapp.models; public class Diary { private int id; private String title; private String content; private String date; private String imagePath; public Diary(){} public int getId() { return id; } public void setId(int id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getDate() { return date; } public void setDate(String date) { this.date = date; } public String getImagePath() { return imagePath; } public void setImagePath(String imagePath) { this.imagePath = imagePath; } } DatabaseHelper:package com.example.diaryapp.utils; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; import com.example.diaryapp.models.Diary; import java.util.ArrayList; import java.util.List; public class DatabaseHelper extends SQLiteOpenHelper { //数据库信息 private static final String DATABASE_NAME = "DiaryApp.db"; private static final int DATABASE_VERSION = 1; //用户表信息 private static final String TABLE_USER = "user"; private static final String COLUMN_USER_ID = "id"; private static final String COLUMN_USER_USERNAME = "username"; private static final String COLUMN_USER_PASSWORD = "password"; //日记表信息 private static final String TABLE_DIARY = "diary"; private static final String COLUMN_DIARY_ID = "id"; private static final String COLUMN_DIARY_USER_ID = "user_id"; private static final String COLUMN_DIARY_TITLE = "title"; private static final String COLUMN_DIARY_CONTENT = "content"; private static final String COLUMN_DIARY_DATE = "date"; private static final String COLUMN_DIARY_IMAGE_PATH = "image_path"; public DatabaseHelper(Context context) { super(context, DATABASE_NAME,null,DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { //创建用户表 String createUserTable = "CREATE TABLE " + TABLE_USER + " (" + COLUMN_USER_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + COLUMN_USER_USERNAME + " TEXT UNIQUE, " + COLUMN_USER_PASSWORD + " TEXT)"; db.execSQL(createUserTable); //创建日记表 String createDiaryTable = "CREATE TABLE " + TABLE_DIARY + " (" + COLUMN_DIARY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + COLUMN_DIARY_USER_ID + " INTEGER, " + COLUMN_DIARY_TITLE + " TEXT, " + COLUMN_DIARY_CONTENT + " TEXT, " + COLUMN_DIARY_DATE + " TEXT, " + COLUMN_DIARY_IMAGE_PATH + " TEXT)"; db.execSQL(createDiaryTable); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { //升级数据库时的操作 db.execSQL("DROP TABLE IF EXISTS " + TABLE_USER); db.execSQL("DROP TABLE IF EXISTS " + TABLE_DIARY); onCreate(db); } //用户相关操作 public long addUser(String username,String password){ SQLiteDatabase db = null; try { db=this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(COLUMN_USER_USERNAME, username); values.put(COLUMN_USER_PASSWORD,password); return db.insert(TABLE_USER,null,values); }catch (Exception e){ e.printStackTrace(); return -1; }finally { if (db!=null){ db.close(); } } } public boolean checkUser(String username, String password){ SQLiteDatabase db = null; Cursor cursor = null; boolean exists = false; try { db=this.getReadableDatabase(); cursor = db.rawQuery( "SELECT * FROM " + TABLE_USER + " WHERE " + COLUMN_USER_USERNAME + "=? AND " + COLUMN_USER_PASSWORD + "=?", new String[]{username,password} ); exists = cursor.getCount() > 0; }catch (Exception e){ e.printStackTrace(); }finally { if (cursor != null){ cursor.close(); } if (db!=null){ db.close(); } } return exists; // Cursor cursor = db.rawQuery( // "SELECT * FROM " + TABLE_USER + " WHERE " + // COLUMN_USER_USERNAME + "=? AND " + COLUMN_USER_PASSWORD + "=?", // new String[]{username,password} // ); // boolean exists = cursor.getCount() > 0; // Log.d("DatabaseHelper","checkUser: username=" + username + ", exists=" + exists); // cursor.close(); // return exists; } public int getUserId(String username){ SQLiteDatabase db = null; Cursor cursor=null; int userId=-1; try { db = this.getReadableDatabase(); cursor = db.rawQuery( "SELECT " + COLUMN_USER_ID + " FROM " + TABLE_USER + " WHERE " + COLUMN_USER_USERNAME + "=?", new String[]{username} ); if (cursor.moveToFirst()) { // 如果cursor不为空且移动到第一条记录,获取用户ID userId = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_USER_ID)); } }catch (Exception e){ e.printStackTrace(); }finally { if (cursor!=null){ cursor.close(); } if (db!=null){ db.close(); } } return userId; } //日记相关操作 public long addDiary(int userId, Diary diary){ SQLiteDatabase db = getWritableDatabase(); try { db.beginTransaction(); ContentValues values = new ContentValues(); values.put(COLUMN_DIARY_USER_ID, userId); values.put(COLUMN_DIARY_TITLE,diary.getTitle()); values.put(COLUMN_DIARY_CONTENT,diary.getContent()); values.put(COLUMN_DIARY_DATE,diary.getDate()); values.put(COLUMN_DIARY_IMAGE_PATH,diary.getImagePath()); long result = db.insert(TABLE_DIARY,null,values); db.setTransactionSuccessful(); return result; }catch (Exception e){ e.printStackTrace(); return -1; }finally { db.endTransaction(); // if (db != null){ // db.endTransaction(); // db.close(); // } } } public List<Diary> getDiariesByUserId(int userId){ List<Diary> diaries = new ArrayList<>(); SQLiteDatabase db = getReadableDatabase(); Cursor cursor = null; try { db = this.getReadableDatabase(); cursor = db.rawQuery( "SELECT * FROM " + TABLE_DIARY + " WHERE " + COLUMN_DIARY_USER_ID + "=? ORDER BY " + COLUMN_DIARY_DATE + " DESC", new String[]{String.valueOf(userId)} ); while (cursor.moveToNext()) { Diary diary = new Diary(); diary.setId(cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_DIARY_ID))); diary.setTitle(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_DIARY_TITLE))); diary.setContent(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_DIARY_CONTENT))); diary.setDate(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_DIARY_DATE))); diary.setImagePath(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_DIARY_IMAGE_PATH))); diaries.add(diary); } }catch (Exception e){ e.printStackTrace(); }finally { if (cursor != null){ cursor.close(); } // if (db != null){ // db.close(); // } } return diaries; } public Diary getDiaryById(int diaryId){ SQLiteDatabase db = getReadableDatabase(); Cursor cursor = null; try { db = this.getReadableDatabase(); cursor = db.rawQuery( "SELECT * FROM " + TABLE_DIARY + " WHERE " + COLUMN_DIARY_ID + "=?", new String[]{String.valueOf(diaryId)} ); if (cursor.moveToFirst()) { Diary diary = new Diary(); diary.setId(cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_DIARY_ID))); diary.setTitle(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_DIARY_TITLE))); diary.setContent(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_DIARY_CONTENT))); diary.setDate(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_DIARY_DATE))); diary.setImagePath(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_DIARY_IMAGE_PATH))); return diary; } }catch (Exception e){ e.printStackTrace(); }finally { if (cursor != null){ cursor.close(); } // if (db != null){ //// db.close(); // 确保关闭数据库连接 // } } // cursor.close(); return null; } public long updateDiary(Diary diary){ SQLiteDatabase db = getWritableDatabase(); try { ContentValues values = new ContentValues(); values.put(COLUMN_DIARY_TITLE,diary.getTitle()); values.put(COLUMN_DIARY_CONTENT,diary.getContent()); values.put(COLUMN_DIARY_DATE,diary.getDate()); values.put(COLUMN_DIARY_IMAGE_PATH,diary.getImagePath()); return db.update(TABLE_DIARY, values, COLUMN_DIARY_ID + "=?", new String[]{String.valueOf(diary.getId())}); // db.close(); // 确保关闭数据库连接 // Log.d("DatabaseHelper","updateDiary: result=" + result); //调试日志 // return result; }catch (Exception e){ e.printStackTrace(); return -1; }finally { // if (db != null){ // db.close(); // } } } public void deleteDiary(int diaryId) { SQLiteDatabase db = getWritableDatabase(); try { db.beginTransaction(); db.delete(TABLE_DIARY, COLUMN_DIARY_ID + "=?", new String[]{ String.valueOf(diaryId) }); db.setTransactionSuccessful(); }catch (Exception e){ e.printStackTrace(); }finally { db.endTransaction(); } // finally { // if (db != null){ // db.close(); // } } // int result=db.delete(TABLE_DIARY, // COLUMN_DIARY_ID + "=?", // new String[]{String.valueOf(diaryId)}); // db.close(); // 确保关闭数据库连接 // Log.d("DatabaseHelper","deleteDiary: result=" + result);//调试日志 // } } GlideEngine:package com.example.diaryapp.utils; import android.content.Context; import android.widget.ImageView; import com.bumptech.glide.Glide; import com.bumptech.glide.load.resource.bitmap.CenterCrop; import com.bumptech.glide.load.resource.bitmap.RoundedCorners; import com.example.diaryapp.R; import com.luck.picture.lib.engine.ImageEngine; import com.luck.picture.lib.utils.ActivityCompatHelper; public class GlideEngine implements ImageEngine { @Override public void loadImage(Context context, String url, ImageView imageView) { if (!ActivityCompatHelper.assertValidRequest(context)) { return; } try { Glide.with(context) .load(url) .into(imageView); } catch (Exception e) { e.printStackTrace(); imageView.setBackgroundColor(context.getResources().getColor(android.R.color.darker_gray)); } } @Override public void loadImage(Context context, ImageView imageView, String url, int maxWidth, int maxHeight) { if (!ActivityCompatHelper.assertValidRequest(context)) { return; } Glide.with(context) .load(url) .override(maxWidth, maxHeight) .into(imageView); } @Override public void loadAlbumCover(Context context, String url, ImageView imageView) { if (!ActivityCompatHelper.assertValidRequest(context)) { return; } Glide.with(context) .asBitmap() .load(url) .override(180, 180) .sizeMultiplier(0.5f) .transform(new CenterCrop(), new RoundedCorners(8)) .placeholder(R.drawable.ic_launcher_background) .into(imageView); } @Override public void loadGridImage(Context context, String url, ImageView imageView) { if (!ActivityCompatHelper.assertValidRequest(context)) { return; } Glide.with(context) .load(url) .override(200, 200) .centerCrop() .placeholder(R.drawable.ic_launcher_background) .into(imageView); } @Override public void pauseRequests(Context context) { Glide.with(context).pauseRequests(); } @Override public void resumeRequests(Context context) { Glide.with(context).resumeRequests(); } private GlideEngine() { } private static final class InstanceHolder { static final GlideEngine instance = new GlideEngine(); } public static GlideEngine createGlideEngine() { return InstanceHolder.instance; } }DiaryActivity:package com.example.diaryapp; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; import android.provider.MediaStore; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; import com.example.diaryapp.models.Diary; import com.example.diaryapp.utils.DatabaseHelper; import com.example.diaryapp.utils.GlideEngine; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.Date; public class DiaryActivity extends AppCompatActivity { // 声明界面组件和相关变量 private EditText etTitle,etContent; private TextView tvDate; private ImageView ivImage; private Button btnSave,btnDelete,btnAddImage; private DatabaseHelper dbHelper; private int userId; private int diaryId = -1; private String imagePath; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_diary);// 设置布局文件 // 获取传递过来的用户ID和日记ID userId = getIntent().getIntExtra("userId",-1); diaryId = getIntent().getIntExtra("diaryId",-1); if (userId == -1){ // 检查用户ID是否有效 // 用户ID无效,可能是登录流程的问题 Toast.makeText(this, "用户ID无效", Toast.LENGTH_SHORT).show(); finish(); return; } // 初始化界面组件 initView(); // 如果是编辑日记,加载日记内容 if (diaryId != -1){ Diary diary=dbHelper.getDiaryById(diaryId); if (diary != null){ etTitle.setText(diary.getTitle()); // 设置日记标题 etContent.setText(diary.getContent()); // 设置日记内容 tvDate.setText(diary.getDate()); // 设置日记日期 imagePath=diary.getImagePath(); // 获取图片路径 if (imagePath != null && !imagePath.isEmpty()) { try { // 使用Glide加载图片 GlideEngine engine = GlideEngine.createGlideEngine(); engine.loadImage(this, imagePath, ivImage); } catch (Exception e) { e.printStackTrace(); ivImage.setBackgroundColor(getResources().getColor(android.R.color.darker_gray)); // 设置默认背景 } } // 显示删除按钮 btnDelete.setVisibility(View.VISIBLE); }else { // 日记ID无效,可能是数据库查询的问题 Toast.makeText(this, "日记ID无效", Toast.LENGTH_SHORT).show(); finish(); } }else { // 设置当前日期 tvDate.setText(getCurrentDate()); } // 添加图片按钮监听 btnAddImage.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //selectImage(); // 改:if (!view.isEnabled() || !view.isClickable()){ // return; // } showImageOptions(); // 显示添加图片选项 } }); // 保存按钮监听 btnSave.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // 改:if (!view.isEnabled() || !view.isClickable()){ // return; // } saveDiary(); // 保存日记 } }); // 删除按钮监听 btnDelete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // 改:if (!view.isEnabled() || !view.isClickable()){ // return; // } deleteDiary(); // 删除日记 } }); } // private void selectImage() { // // 创建选择图片的Intent // Intent pickIntent=new Intent(Intent.ACTION_GET_CONTENT); // pickIntent.setType("image/*"); // // // 创建拍照的Intent // Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); // if (captureIntent.resolveActivity(getPackageManager()) != null) { // startActivityForResult(captureIntent,2); // } // //intent.setAction(Intent.ACTION_GET_CONTENT); // startActivityForResult(pickIntent,1); // } private void showImageOptions() { // 创建弹出菜单,提供两个选项:从图库选择和拍照 String[] options = {"从图库选择","拍照"}; new AlertDialog.Builder(this) .setTitle("添加图片") .setItems(options,(dialog,which) -> { if (which == 0){ //从图库选择 Intent pickIntent = new Intent(Intent.ACTION_GET_CONTENT); pickIntent.setType("image/*"); startActivityForResult(pickIntent,1); }else if (which == 1){ //拍照 Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (captureIntent.resolveActivity(getPackageManager()) != null){ startActivityForResult(captureIntent,2); }else { Toast.makeText(DiaryActivity.this, "无法调用摄像头", Toast.LENGTH_SHORT).show(); } } }) .show(); } private void saveDiary() { String title=etTitle.getText().toString().trim(); String content=etContent.getText().toString().trim(); String date=tvDate.getText().toString(); if (title.isEmpty() || content.isEmpty()){ // 检查输入是否为空 return; } Diary diary=new Diary(); diary.setTitle(title); diary.setContent(content); diary.setDate(date); diary.setImagePath(imagePath); // 确保图片路径不为空 if (diaryId != -1){ diary.setId(diaryId); dbHelper.updateDiary(diary); // 更新日记 }else { dbHelper.addDiary(userId,diary); // 添加新日记 } //返回主界面 Intent resultIntent = new Intent(); setResult(RESULT_OK,resultIntent); finish(); } private void deleteDiary() { // //删除日记 // dbHelper.deleteDiary(diaryId); new AlertDialog.Builder(this) .setTitle("确认删除") .setMessage("确定要删除这篇日记吗?") .setPositiveButton("删除", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // 使用独立的 DatabaseHelper 实例执行删除操作 DatabaseHelper helper=new DatabaseHelper(DiaryActivity.this); helper.deleteDiary(diaryId); // dbHelper.deleteDiary(diaryId); // 删除日记 // 设置结果码为RESULT_OK,表示操作成功,返回主界面MainActivity Intent resultIntent = new Intent(); setResult(RESULT_OK,resultIntent); finish(); } }) .setNegativeButton("取消",null) .show(); } @Override protected void onDestroy() { super.onDestroy(); // 释放可能持有的资源 if (btnSave!=null){ btnSave.setOnClickListener(null); } if (btnDelete!=null){ btnDelete.setOnClickListener(null); } if (btnAddImage!=null){ btnAddImage.setOnClickListener(null); } } @Override protected void onPause() { super.onPause(); // 暂停时释放资源 if (isFinishing()) { // 清理可能的临时资源 } } @Override protected void onResume() { super.onResume(); // 恢复时重新初始化资源 } private String getCurrentDate(){ // 返回当前日期的字符串表示 return new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); } protected void onActivityResult(int requestCode,int resultCode,Intent data){ super.onActivityResult(requestCode,resultCode,data); if (isFinishing() || isDestroyed()){ return; } // 确保调用super.onActivityResult if (requestCode == 1 && resultCode == RESULT_OK && data != null){ // 处理从图库选择的图片 Uri selectedImage = data.getData(); if (selectedImage != null) { // 检查selectedImage是否为null try { // 将图片保存到应用私有目录 Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), selectedImage); String filename = "diary_image_" + System.currentTimeMillis() + ".jpg"; File file = new File(getExternalFilesDir(null), filename); // File fileDir = getExternalFilesDir(null); // if (fileDir != null && !fileDir.exists()){ // fileDir.mkdir(); // } // File file = new File(fileDir,filename); FileOutputStream fos = new FileOutputStream(file); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos); fos.flush(); fos.close(); imagePath = file.getAbsolutePath(); // 改:ivImage.setImageBitmap(bitmap); // 显示图片 // 使用Glide加载图片 GlideEngine engine = GlideEngine.createGlideEngine(); engine.loadImage(this, imagePath, ivImage); } catch (IOException e) { e.printStackTrace(); //改: ivImage.setBackgroundColor(getResources().getColor(android.R.color.darker_gray)); } } // Glide.with(this).load(imagePath).into(ivImage); // }catch (IOException e){ // e.printStackTrace(); // } }else if (requestCode == 2 && resultCode == RESULT_OK && data != null){ //处理拍照结果 Bundle extras = data.getExtras(); Bitmap imageBitmap = (Bitmap) extras.get("data"); if (extras != null) { // 检查extras是否为null // Bitmap imageBitmap = (Bitmap) extras.get("data"); if (imageBitmap != null) {// 检查photo是否为null try { String filename = "diary_image_" + System.currentTimeMillis() + ".jpg"; File file = new File(getExternalFilesDir(null), filename); FileOutputStream fos = new FileOutputStream(file); imageBitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos); fos.flush(); fos.close(); imagePath = file.getAbsolutePath(); // 改:ivImage.setImageBitmap(imageBitmap); // 显示图片 // 使用Glide加载图片 GlideEngine engine = GlideEngine.createGlideEngine(); engine.loadImage(this, imagePath, ivImage); } catch (IOException e) { e.printStackTrace(); ivImage.setBackgroundColor(getResources().getColor(android.R.color.darker_gray)); } } } } } private void initView() { // 初始化界面组件 etTitle=findViewById(R.id.etTitle); etContent=findViewById(R.id.etContent); tvDate=findViewById(R.id.tvDate); ivImage=findViewById(R.id.ivImage); btnAddImage=findViewById(R.id.btnAddImage); btnSave=findViewById(R.id.btnSave); btnDelete=findViewById(R.id.btnDelete); dbHelper=new DatabaseHelper(this); // 初始化数据库助手 // userId=getIntent().getIntExtra("userId",-1); // diaryId=getIntent().getIntExtra("diaryId",-1); } } LoginActivity:package com.example.diaryapp; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; import android.widget.Toast; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import com.example.diaryapp.utils.DatabaseHelper; public class LoginActivity extends AppCompatActivity { private EditText etUsername,etPassword; private CheckBox cbRemember; private DatabaseHelper dbHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_login); // 初始化界面组件 initView(); // 获取SharedPreferences中的保存信息 SharedPreferences prefs=getSharedPreferences("DiaryApp",MODE_PRIVATE); String username=prefs.getString("username",""); String password=prefs.getString("password",""); // 如果用户名存在但密码不存在,删除用户名 if (username.isEmpty() || password.isEmpty()){ prefs.edit().remove("username").remove("password").apply(); } boolean remember=prefs.getBoolean("remember",false); etUsername.setText(username); etPassword.setText(password); cbRemember.setChecked(remember); // 登录按钮监听 Button btnLogin=findViewById(R.id.btnLogin); btnLogin.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { login(); } }); // 注册按钮监听 Button btnRegister=findViewById(R.id.btnRegister); btnRegister.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent=new Intent(LoginActivity.this, RegisterActivity.class); startActivity(intent); } }); } private void login() { String username=etUsername.getText().toString().trim(); String password=etPassword.getText().toString().trim(); boolean remember=cbRemember.isChecked(); // 验证输入 if (username.isEmpty() || password.isEmpty()){ Toast.makeText(this, "用户名和密码不能为空", Toast.LENGTH_SHORT).show(); return; } // 保存登录信息到SharedPreferences SharedPreferences prefs=getSharedPreferences("DiaryApp",MODE_PRIVATE); SharedPreferences.Editor editor=prefs.edit(); // 如果记住密码被勾选,则保存密码;否则清空密码 if (remember){ editor.putString("username",username); editor.putString("password",password); }else { // editor.remove("username"); editor.remove("password"); // 确保未勾选时清空密码 } editor.putBoolean("remember",remember); editor.apply(); // 验证用户 if (dbHelper.checkUser(username,password)){ Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show(); Intent intent=new Intent(LoginActivity.this,MainActivity.class); intent.putExtra("username",username); startActivity(intent); finish(); // 关闭登录界面,避免返回时重复登录 }else { Toast.makeText(this, "用户名或密码错误", Toast.LENGTH_SHORT).show(); } } private void initView() { // 初始化界面组件 etUsername=findViewById(R.id.etUsername); etPassword=findViewById(R.id.etPassword); cbRemember=findViewById(R.id.cbRemember); dbHelper=new DatabaseHelper(this); } }MainActivity:package com.example.diaryapp; import android.annotation.SuppressLint; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.example.diaryapp.adapters.DiaryAdapter; import com.example.diaryapp.models.Diary; import com.example.diaryapp.utils.DatabaseHelper; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private RecyclerView rvDiaryList; private DiaryAdapter diaryAdapter; private List<Diary> diaryList; private DatabaseHelper dbHelper; private String username; private int userId; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_main); dbHelper=new DatabaseHelper(this); // 获取传递过来的用户名 username=getIntent().getStringExtra("username"); if (username == null){ // 用户名为空,可能是登录流程的问题 Toast.makeText(this, "用户名为空", Toast.LENGTH_SHORT).show(); finish(); return; } userId=dbHelper.getUserId(username); if (userId == -1){ // 用户ID无效,可能是数据库查询的问题 Toast.makeText(this, "用户ID无效", Toast.LENGTH_SHORT).show(); finish(); return; } // 初始化界面组件 initView(); // 加载日记 loadDiaries(); // 添加日记按钮监听 Button btnAddDiary=findViewById(R.id.btnAddDiary); // 在添加日记的按钮点击事件中 btnAddDiary.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent=new Intent(MainActivity.this,DiaryActivity.class); intent.putExtra("userId",userId); startActivityForResult(intent,1); } }); } protected void onActivityResult(int requestCode,int resultCode,Intent data){ super.onActivityResult(requestCode,resultCode,data); if (requestCode == 1 && resultCode == RESULT_OK){ // List<Diary> diaries = dbHelper.getDiariesByUserId(userId); // diaryAdapter.updateDiaries(diaries); //更新适配器数据 loadDiaries(); // 刷新日记列表 } } @SuppressLint("NotifyDataSetChanged") private void loadDiaries() { diaryList.clear(); // 清空当前列表 diaryList.addAll(dbHelper.getDiariesByUserId(userId)); // 从数据库重新获取数据 diaryAdapter.notifyDataSetChanged(); // 通知适配器数据已更改 } private void initView() { rvDiaryList=findViewById(R.id.rvDiaryList); rvDiaryList.setLayoutManager(new LinearLayoutManager(this)); diaryList=new ArrayList<>(); // 确保传递 userId 给 DiaryAdapter diaryAdapter=new DiaryAdapter(this,diaryList,userId); rvDiaryList.setAdapter(diaryAdapter); } }RegisterActivity:package com.example.diaryapp; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import com.example.diaryapp.utils.DatabaseHelper; public class RegisterActivity extends AppCompatActivity { private EditText etUsername,etPassword,etConfirmPassword; private Button btnRegister; private DatabaseHelper dbHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_register); // 初始化界面组件 initView(); // 注册按钮监听 btnRegister=findViewById(R.id.btnRegister); btnRegister.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { registerUser(); } }); // 返回登录按钮监听 Button btnBack=findViewById(R.id.btnBack); btnBack.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent=new Intent(RegisterActivity.this, LoginActivity.class); startActivity(intent); } }); } private void registerUser() { String username=etUsername.getText().toString().trim(); String password=etPassword.getText().toString().trim(); String confirmPassword=etConfirmPassword.getText().toString().trim(); // 验证输入 if (username.isEmpty() || password.isEmpty() || confirmPassword.isEmpty()){ Toast.makeText(this, "所有字段不能为空", Toast.LENGTH_SHORT).show(); return; } if (!password.equals(confirmPassword)){ Toast.makeText(this, "两次输入的密码不一致", Toast.LENGTH_SHORT).show(); return; } // 检查用户名是否已被注册 if (dbHelper.checkUser(username,password)){ Toast.makeText(this, "该用户名已被注册", Toast.LENGTH_SHORT).show(); return; } // 注册新用户 long result=dbHelper.addUser(username,password); if (result > 0){ Toast.makeText(this, "注册成功", Toast.LENGTH_SHORT).show(); Intent intent=new Intent(RegisterActivity.this,LoginActivity.class); startActivity(intent); finish(); }else { Toast.makeText(this, "注册失败", Toast.LENGTH_SHORT).show(); } } private void initView() { etUsername=findViewById(R.id.etUsername); etPassword=findViewById(R.id.etPassword); etConfirmPassword=findViewById(R.id.etConfirmPassword); dbHelper=new DatabaseHelper(this); } }
最新发布
06-06
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值