Android仿抖音评论列表
效果图:

BottomSheetDialogFragment是可以上下滑动退出的dialogFragment,里面就是behavior起到了作用,感兴趣的可以去了解了解,我这里记录一下写过的demo。仔细观察抖音的评论列表,它底部的请输入框其实是个Textview,点击后才弹出输入的dialog弹窗,如果不这样做,会有一个问题,就是列表往下滑的时候,底部的布局并没有跟着移动。当然,我们也能做一个recycleview的底部条目为输入框,这里我采取了点击弹出真正的对话框的方式。好,下面直接上代码。
评论列表
列表fragment
public class CommendMsgDialogFragment extends BottomSheetDialogFragment {private RecyclerView mRecyclerView;private CommendAdapter mCommendAdapter;private FragmentCommendMsgDialogBinding mBinding;private String mUniquekey;private CommendInputDialogFragment mCommendInputDialog;public CommendMsgDialogFragment() {// Required empty public constructor}public static CommendMsgDialogFragment newInstance(String uniquekey) {CommendMsgDialogFragment fragment = new CommendMsgDialogFragment();Bundle args = new Bundle();args.putString("uniquekey",uniquekey);fragment.setArguments(args);return fragment;}@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//设置背景透明,才能显示出layout中诸如圆角的布局,否则会有白色底(框)setStyle(BottomSheetDialogFragment.STYLE_NORMAL, R.style.CustomBottomSheetDialogTheme);if (getArguments() != null) {mUniquekey = getArguments().getString("uniquekey");mUniquekey = "c322b12d56a3125e73e7b2978ff846c0";}}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {// Inflate the layout for this fragmentmBinding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.fragment_commend_msg_dialog, container, false);return mBinding.getRoot();}@Overridepublic void onActivityCreated(@Nullable Bundle savedInstanceState) {Window window = getDialog().getWindow();window.requestFeature(Window.FEATURE_NO_TITLE);super.onActivityCreated(savedInstanceState);//设置背景为透明window.setWindowAnimations(R.style.AnimBottom);window.setBackgroundDrawable(ContextCompat.getDrawable(getContext(), android.R.color.transparent));//去除阴影window.setGravity(Gravity.BOTTOM);WindowManager.LayoutParams layoutParams = window.getAttributes();layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;layoutParams.dimAmount = 0.1f;window.setAttributes(layoutParams);getDialog().setOnDismissListener(new DialogInterface.OnDismissListener() {@Overridepublic void onDismiss(DialogInterface dialog) {if(mOnDisMissCallBack != null){mOnDisMissCallBack.onDismiss();}dismissAllowingStateLoss();}});}@Overridepublic void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);mRecyclerView = mBinding.mBottomSheet;mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));mCommendAdapter = new CommendAdapter();mRecyclerView.setAdapter(mCommendAdapter);mBinding.mTextView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {showInputDialog();}});mBinding.mTvSend.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {savePost();}});initData();}private void showInputDialog() {if(mCommendInputDialog == null){mCommendInputDialog = CommendInputDialogFragment.newInstance(mUniquekey,mBinding.mTextView.getText().toString());mCommendInputDialog.setOnDisMissCallBack(new CommendInputDialogFragment.OnDisMissCallBack() {@Overridepublic void onDismiss() {mCommendInputDialog = null;}@Overridepublic void saveCommend() {savePost();}@Overridepublic void onCommendTextChanged(String contend) {mBinding.mTextView.setText(contend);}});mCommendInputDialog.show(getChildFragmentManager(),mCommendInputDialog.getTag());}}/*** 根据key值获取他的所有评论,按时间排序*/private void initData() {if (BmobUser.isLogin()) {BmobQuery<CommentBean> query = new BmobQuery<>();query.addWhereEqualTo("uniquekey", mUniquekey);query.order("-updatedAt");//包含作者信息query.include("user");query.findObjects(new FindListener<CommentBean>() {@Overridepublic void done(List<CommentBean> object, BmobException e) {if (e == null) {mCommendAdapter.refreshList(object);} else {Toast.makeText(App.getInstance(), "请求失败:"+e.getMessage(), Toast.LENGTH_SHORT).show();}}});} else {Toast.makeText(App.getInstance(), "请先登录~", Toast.LENGTH_SHORT).show();}}/*** 添加一对一关联,当前用户发布帖子*/private void savePost() {String content = mBinding.mTextView.getText().toString();if (TextUtils.isEmpty(content)) {Toast.makeText(App.getInstance(), "内容不能为空", Toast.LENGTH_SHORT).show();return;}if (BmobUser.isLogin()) {CommentBean post = new CommentBean();post.setContent(content);post.setUniquekey(mUniquekey);//添加一对一关联,用户关联帖子post.setUser(BmobUser.getCurrentUser(User.class));post.save(new SaveListener<String>() {@Overridepublic void done(String s, BmobException e) {if (e == null) {Toast.makeText(App.getInstance(), "评论成功~", Toast.LENGTH_SHORT).show();} else {Toast.makeText(App.getInstance(), "评论失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();}}});} else {Toast.makeText(App.getInstance(), "请先登录~", Toast.LENGTH_SHORT).show();}}public interface OnDisMissCallBack{void onDismiss();}private OnDisMissCallBack mOnDisMissCallBack;public void setOnDisMissCallBack(OnDisMissCallBack mOnDisMissCallBack) {this.mOnDisMissCallBack = mOnDisMissCallBack;}}
这里的initData方法是数据源,我这里用的是bmbo第三方sdk,他可以把数据存到他们的数据库中,并且可以用sql代码查询,少量的数据是免费的,适合学生党来写毕业论文。然后是布局:
<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"xmlns:app="http://schemas.android.com/apk/res-auto"android:fitsSystemWindows="true"><data></data><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"tools:context=".fragments.dialogfragments.CommendMsgDialogFragment"><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/mBottomSheet"android:layout_width="match_parent"android:layout_height="400dp"android:orientation="vertical"app:behavior_hideable="true"app:behavior_peekHeight="66dp"android:paddingBottom="50dp"app:layout_behavior="@string/bottom_sheet_behavior"android:background="@drawable/bg_round15_top_white"app:layout_constraintTop_toTopOf="parent"></androidx.recyclerview.widget.RecyclerView><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="50dp"android:background="@drawable/storke_line_1dp_dcdcdc"android:layout_gravity="bottom"app:layout_constraintBottom_toBottomOf="parent"><TextViewandroid:id="@+id/mTvSend"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="发送"android:textSize="16sp"android:textColor="@color/write"android:paddingTop="5dp"android:paddingBottom="5dp"android:paddingLeft="15dp"android:paddingRight="15dp"android:layout_alignParentRight="true"android:layout_marginRight="15dp"android:layout_centerVertical="true"android:background="@drawable/send_commend_bg" /><TextViewandroid:id="@+id/mTextView"android:layout_width="match_parent"android:layout_height="wrap_content"android:minHeight="35dp"android:background="@drawable/et_search_bg"android:layout_toLeftOf="@id/mTvSend"android:layout_marginRight="15dp"android:layout_centerVertical="true"android:layout_marginLeft="15dp"android:text=""android:gravity="center_vertical"android:hint="发个评论,说不定能火"android:paddingTop="3dp"android:paddingBottom="3dp"android:maxLines="2"android:paddingLeft="10dp"android:paddingRight="10dp"android:textColor="@color/ff333333"android:textSize="14sp" /></RelativeLayout></androidx.constraintlayout.widget.ConstraintLayout></layout>
adapter代码
public class CommendAdapter extends RecyclerView.Adapter<CommendAdapter.ViewHolder> {private List<CommentBean> list = new ArrayList<>();@NonNull@Overridepublic ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.commend_item_layout, parent, false);CommendItemLayoutBinding bind = DataBindingUtil.bind(view);return new ViewHolder(bind);}@Overridepublic void onBindViewHolder(@NonNull ViewHolder holder, int position) {CommentBean commentBean = list.get(position);if(commentBean != null){if(commentBean.getUser().getAvatar() != null){ImageLoader.getInstance().loadCircleImage(holder.mBinding.mIvHead.getContext(),commentBean.getUser().getAvatar().getUrl(),R.mipmap.default_head_img,holder.mBinding.mIvHead);}holder.mBinding.mTvNickName.setText(commentBean.getUser().getNickname());holder.mBinding.mTvContent.setText(commentBean.getContent());}}@Overridepublic int getItemCount() {return list.size();}public void refreshList(List<CommentBean> object) {this.list.clear();this.list.addAll(object);notifyDataSetChanged();}public class ViewHolder extends RecyclerView.ViewHolder {CommendItemLayoutBinding mBinding;public ViewHolder(@NonNull View itemView) {super(itemView);}public ViewHolder(@NonNull CommendItemLayoutBinding bind) {super(bind.getRoot());mBinding = bind;}}}
然后是条目布局
<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"><data></data><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:paddingLeft="10dp"android:paddingRight="10dp"android:paddingTop="10dp"android:orientation="vertical"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"><ImageViewandroid:id="@+id/mIvHead"android:layout_width="50dp"android:layout_height="50dp"android:src="@mipmap/default_head_img" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginLeft="5dp"android:orientation="vertical"><TextViewandroid:id="@+id/mTvNickName"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="昵称"android:textColor="@color/ff333333"android:textSize="18sp" /><TextViewandroid:id="@+id/mTvContent"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text=""android:textColor="@color/ff949494"android:textSize="14sp"android:layout_marginTop="5dp" /></LinearLayout></LinearLayout><Viewandroid:layout_width="match_parent"android:layout_height="0.5dp"android:layout_marginTop="10dp"android:layout_marginBottom="10dp"android:background="@color/f5f5f5" /></LinearLayout></layout>
很简单的布局
好当我们点击底部的输入框时,弹出真正的输入弹窗。
mBinding.mTextView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {showInputDialog();}});
private void showInputDialog() {if(mCommendInputDialog == null){mCommendInputDialog = CommendInputDialogFragment.newInstance(mUniquekey,mBinding.mTextView.getText().toString());mCommendInputDialog.setOnDisMissCallBack(new CommendInputDialogFragment.OnDisMissCallBack() {@Overridepublic void onDismiss() {mCommendInputDialog = null;}@Overridepublic void saveCommend() {savePost();}@Overridepublic void onCommendTextChanged(String contend) {mBinding.mTextView.setText(contend);}});mCommendInputDialog.show(getChildFragmentManager(),mCommendInputDialog.getTag());}}
接下来上输入框弹窗的代码
输入框
public class CommendInputDialogFragment extends DialogFragment {private FragmentCommendInputDialogBinding mBinding;private int mAllowableErrorHeight;public CommendInputDialogFragment() {// Required empty public constructor}private String mUniquekey;private String mContent;private int mLastDiff = 0;public static final String UNIQUEKEY = "uniquekey";public static final String CONTENT = "content";public static CommendInputDialogFragment newInstance(String uniquekey,String content) {CommendInputDialogFragment fragment = new CommendInputDialogFragment();Bundle args = new Bundle();args.putString(UNIQUEKEY, uniquekey);args.putString(CONTENT, content);fragment.setArguments(args);return fragment;}public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);if (getArguments() != null) {mUniquekey = getArguments().getString(UNIQUEKEY);mUniquekey = "c322b12d56a3125e73e7b2978ff846c0";mContent = getArguments().getString(CONTENT);}}public void onActivityCreated(@Nullable Bundle savedInstanceState) {Window window = getDialog().getWindow();super.onActivityCreated(savedInstanceState);getDialog().setCancelable(true);getDialog().setCanceledOnTouchOutside(true);//设置背景为透明window.setWindowAnimations(R.style.AnimBottom);window.setBackgroundDrawable(ContextCompat.getDrawable(getContext(), android.R.color.transparent));//去除阴影window.setGravity(Gravity.BOTTOM);WindowManager.LayoutParams layoutParams = window.getAttributes();layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;layoutParams.dimAmount = 0.5f;window.setAttributes(layoutParams);window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE|WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);getDialog().setOnDismissListener(new DialogInterface.OnDismissListener() {public void onDismiss(DialogInterface dialog) {dismissDialog();}});}public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {// Inflate the layout for this fragmentmBinding = DataBindingUtil.bind(inflater.inflate(R.layout.fragment_commend_input_dialog, container, false));return mBinding.getRoot();}public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);mBinding.mTvSend.setOnClickListener(new View.OnClickListener() {public void onClick(View v) {String content = mBinding.mEditText.getText().toString();if (TextUtils.isEmpty(content)) {Toast.makeText(App.getInstance(), "内容不能为空", Toast.LENGTH_SHORT).show();return;}if (mOnDisMissCallBack != null) {mOnDisMissCallBack.saveCommend();}dismissDialog();}});mBinding.mEditText.setFocusable(true);mBinding.mEditText.setFocusableInTouchMode(true);mBinding.mEditText.requestFocus();mBinding.mEditText.postDelayed(new Runnable() {public void run() {InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);imm.toggleSoftInput(0, InputMethodManager.SHOW_FORCED);Rect r = new Rect();//获取当前界面可视部分getActivity().getWindow().getDecorView().getRootView().getWindowVisibleDisplayFrame(r);//获取屏幕的高度int screenHeight = getActivity().getWindow().getDecorView().getRootView().getHeight();//此处就是用来获取键盘的高度的, 在键盘没有弹出的时候 此高度为0 键盘弹出的时候为一个正数mAllowableErrorHeight = screenHeight - r.bottom;setOnKeyBordListener();}},100);mBinding.mEditText.addTextChangedListener(new TextWatcher() {public void beforeTextChanged(CharSequence s, int start, int count, int after) {}public void onTextChanged(CharSequence s, int start, int before, int count) {String content = mBinding.mEditText.getText().toString();if(mOnDisMissCallBack != null){mOnDisMissCallBack.onCommendTextChanged(content);}}public void afterTextChanged(Editable s) {}});//设置已经填过的文字,以及移动光标mBinding.mEditText.setText(mContent);if(!TextUtils.isEmpty(mContent) && mContent.length() > 0){mBinding.mEditText.setSelection(mContent.length());}}private void setOnKeyBordListener() {getActivity().getWindow().getDecorView().addOnLayoutChangeListener(new View.OnLayoutChangeListener() {public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {try {Rect r = new Rect();//获取当前界面可视部分getActivity().getWindow().getDecorView().getRootView().getWindowVisibleDisplayFrame(r);//获取屏幕的高度int screenHeight = getActivity().getWindow().getDecorView().getRootView().getHeight();//此处就是用来获取键盘的高度的, 在键盘没有弹出的时候 此高度为0 键盘弹出的时候为一个正数int heightDifference = screenHeight - r.bottom;if (heightDifference > mAllowableErrorHeight && mLastDiff >= 0) {//开软键盘} else {//关闭软键盘dismissDialog();}mLastDiff = heightDifference;}catch (Exception e){e.printStackTrace();}}});// mBinding.mRoot.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {// @Override// public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {// Rect r = new Rect();// //获取当前界面可视部分// getActivity().getWindow().getDecorView().getWindowVisibleDisplayFrame(r);// //获取屏幕的高度// int screenHeight = getActivity().getWindow().getDecorView().getRootView().getHeight();//// //此处就是用来获取键盘的高度的, 在键盘没有弹出的时候 此高度为0 键盘弹出的时候为一个正数// int heightDifference = screenHeight - r.bottom;// if (heightDifference <= mAllowableErrorHeight && mLastDiff >= 0) {// //开软键盘// } else {// //关闭软键盘// dismissDialog();// }// Log.d("yanjin","heightDifference = "+heightDifference+" mLastDiff="+mLastDiff);// mLastDiff = heightDifference;// }// });}private void dismissDialog() {if (mOnDisMissCallBack != null) {mOnDisMissCallBack.onDismiss();}dismissAllowingStateLoss();}public void dismiss() {super.dismiss();mLastDiff = 0;}public interface OnDisMissCallBack {void onDismiss();void saveCommend();void onCommendTextChanged(String contend);}private OnDisMissCallBack mOnDisMissCallBack;public void setOnDisMissCallBack(OnDisMissCallBack mOnDisMissCallBack) {this.mOnDisMissCallBack = mOnDisMissCallBack;}}
布局
<layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"><data></data><FrameLayoutandroid:id="@+id/mRoot"android:layout_width="match_parent"android:layout_height="wrap_content"tools:context=".adapter.CommendInputDialogFragment"><RelativeLayoutandroid:id="@+id/rldlgview"android:layout_width="match_parent"android:layout_height="50dp"android:background="@drawable/storke_line_1dp_dcdcdc"><TextViewandroid:id="@+id/mTvSend"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="发送"android:textSize="16sp"android:textColor="@color/write"android:paddingTop="5dp"android:paddingBottom="5dp"android:paddingLeft="15dp"android:paddingRight="15dp"android:layout_alignParentRight="true"android:layout_marginRight="15dp"android:layout_centerVertical="true"android:background="@drawable/send_commend_bg" /><EditTextandroid:id="@+id/mEditText"android:layout_width="match_parent"android:layout_height="wrap_content"android:minHeight="35dp"android:background="@drawable/et_search_bg"android:layout_toLeftOf="@id/mTvSend"android:layout_marginRight="15dp"android:layout_centerVertical="true"android:layout_marginLeft="15dp"android:text=""android:hint="发个评论,说不定能火"android:paddingTop="3dp"android:paddingBottom="3dp"android:maxLines="2"android:gravity="center_vertical"android:paddingLeft="10dp"android:paddingRight="10dp"android:textColor="@color/ff333333"android:textSize="14sp" /></RelativeLayout></FrameLayout></layout>
仔细观察抖音,他是点击弹窗外部,隐藏dialog和软键盘、点击物理返回键隐藏dialog、点击软键盘的收起按钮隐藏dialog。这三点很重要
//解决点击弹窗外部,隐藏dialog和软键盘getDialog().setCanceledOnTouchOutside(true);
而点击物理返回键隐藏dialog、点击软键盘的收起按钮隐藏dialog这两个其实都只要监听软键盘隐藏就可以了,那怎么做呢?看下面的代码。
private void setOnKeyBordListener() {mBinding.rldlgview.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {Rect r = new Rect();//获取当前界面可视部分getActivity().getWindow().getDecorView().getWindowVisibleDisplayFrame(r);//获取屏幕的高度int screenHeight = getActivity().getWindow().getDecorView().getRootView().getHeight();//此处就是用来获取键盘的高度的, 在键盘没有弹出的时候 此高度为0 键盘弹出的时候为一个正数int heightDifference = screenHeight - r.bottom;if (heightDifference <= 0 && mLastDiff >= 0) {//开软键盘} else {//关闭软键盘dismissDialog();}mLastDiff = heightDifference;}});}
好,写完收工。
哦,对了怎么调起弹窗呢?看如下代码即可:
if(mCommendMsgDialogFragment == null){mCommendMsgDialogFragment = CommendMsgDialogFragment.newInstance(uniquekey);mCommendMsgDialogFragment.setOnDisMissCallBack(new CommendMsgDialogFragment.OnDisMissCallBack() {public void onDismiss() {mCommendMsgDialogFragment = null;}});mCommendMsgDialogFragment.show(getSupportFragmentManager(), mCommendMsgDialogFragment.getTag());}
到这里就结束啦。
评论
