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;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//设置背景透明,才能显示出layout中诸如圆角的布局,否则会有白色底(框)
setStyle(BottomSheetDialogFragment.STYLE_NORMAL, R.style.CustomBottomSheetDialogTheme);
if (getArguments() != null) {
mUniquekey = getArguments().getString("uniquekey");
mUniquekey = "c322b12d56a3125e73e7b2978ff846c0";
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
mBinding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.fragment_commend_msg_dialog, container, false);
return mBinding.getRoot();
}
@Override
public 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() {
@Override
public void onDismiss(DialogInterface dialog) {
if(mOnDisMissCallBack != null){
mOnDisMissCallBack.onDismiss();
}
dismissAllowingStateLoss();
}
});
}
@Override
public 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() {
@Override
public void onClick(View v) {
showInputDialog();
}
});
mBinding.mTvSend.setOnClickListener(new View.OnClickListener() {
@Override
public 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() {
@Override
public void onDismiss() {
mCommendInputDialog = null;
}
@Override
public void saveCommend() {
savePost();
}
@Override
public 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>() {
@Override
public 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>() {
@Override
public 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.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".fragments.dialogfragments.CommendMsgDialogFragment">
<androidx.recyclerview.widget.RecyclerView
android: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>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@drawable/storke_line_1dp_dcdcdc"
android:layout_gravity="bottom"
app:layout_constraintBottom_toBottomOf="parent">
<TextView
android: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" />
<TextView
android: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
@Override
public 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);
}
@Override
public 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());
}
}
@Override
public 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>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="10dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/mIvHead"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@mipmap/default_head_img" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:orientation="vertical">
<TextView
android:id="@+id/mTvNickName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="昵称"
android:textColor="@color/ff333333"
android:textSize="18sp" />
<TextView
android: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>
<View
android: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() {
@Override
public void onClick(View v) {
showInputDialog();
}
});
private void showInputDialog() {
if(mCommendInputDialog == null){
mCommendInputDialog = CommendInputDialogFragment.newInstance(mUniquekey,mBinding.mTextView.getText().toString());
mCommendInputDialog.setOnDisMissCallBack(new CommendInputDialogFragment.OnDisMissCallBack() {
@Override
public void onDismiss() {
mCommendInputDialog = null;
}
@Override
public void saveCommend() {
savePost();
}
@Override
public 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 fragment
mBinding = 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>
<FrameLayout
android:id="@+id/mRoot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".adapter.CommendInputDialogFragment">
<RelativeLayout
android:id="@+id/rldlgview"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@drawable/storke_line_1dp_dcdcdc">
<TextView
android: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" />
<EditText
android: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());
}
到这里就结束啦。
评论