Android 工作记录:类似新闻自定义频道

85 阅读5分钟

参考来自:# Android 高仿腾讯新闻频道定制页面

本记录主要为自己后面查询使用

adapter_channel_item:

<?xml version="1.0" encoding="utf-8"?>  
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"  
android:layout_width="match_parent"  
android:layout_height="38dp"  
xmlns:tools="http://schemas.android.com/tools">  
  
<TextView  
android:id="@+id/channelName"  
android:layout_width="match_parent"  
android:layout_height="30dp"  
android:layout_gravity="bottom"  
android:layout_marginStart="3dp"  
android:layout_marginBottom="3dp"  
android:background="@drawable/bg_gray_radius_30"  
android:textColor="@color/text_gray"  
android:gravity="center"  
android:textSize="14sp" />  
  
<ImageView  
android:id="@+id/channelDelete"  
android:layout_width="wrap_content"  
android:layout_height="wrap_content"  
android:layout_gravity="end|top"  
android:visibility="gone"  
tools:visibility="visible"  
android:contentDescription="@null"  
android:src="@drawable/ic_delete" />  
  
<ImageView  
android:id="@+id/channelAdd"  
android:layout_width="wrap_content"  
android:layout_height="wrap_content"  
android:layout_gravity="end|top"  
android:visibility="gone"  
tools:visibility="visible"  
android:contentDescription="@null"  
android:src="@drawable/ic_add" />  
</FrameLayout>

adapter_channel_item_hot_title:

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
android:layout_width="match_parent"  
android:layout_height="wrap_content"  
android:orientation="vertical"  
android:layout_marginBottom="10dp"  
android:layout_marginTop="10dp">  
  
<TextView  
style="@style/TextStyle"  
android:layout_width="wrap_content"  
android:layout_height="wrap_content"  
android:layout_gravity="center_vertical"  
android:text="@string/channel_hot" />  
  
<TextView  
style="@style/TextStyle.sp12"  
android:layout_width="wrap_content"  
android:layout_height="wrap_content"  
android:layout_marginTop="5dp"  
android:text="@string/channel_add_like" />  
</LinearLayout>

adapter_channel_item_title:

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
android:layout_width="match_parent"  
android:layout_height="wrap_content"  
android:layout_marginTop="10dp"  
android:layout_marginBottom="10dp"  
android:orientation="vertical">  
  
<TextView  
style="@style/TextStyle"  
android:layout_width="wrap_content"  
android:layout_height="wrap_content"  
android:layout_gravity="center_vertical"  
android:text="@string/channel_mine" />  
  
<TextView  
style="@style/TextStyle.sp12"  
android:layout_width="wrap_content"  
android:layout_height="wrap_content"  
android:layout_marginTop="5dp"  
android:text="@string/channel_drag_sort" />  
</LinearLayout>

ChannelAdapter:



  
import android.animation.Animator;  
import android.animation.AnimatorSet;  
import android.animation.ObjectAnimator;  
import android.content.Context;  
import android.view.LayoutInflater;  
import android.view.View;  
import android.view.ViewGroup;  
import android.widget.ImageView;  
import android.widget.TextView;  
  
import androidx.recyclerview.widget.RecyclerView;  

import com.xx.xx.entity.ChannelBean;  
  
import java.util.Collections;  
import java.util.List;  
  
  
/**  
* <a href="https://juejin.cn/post/6844903833773015048">...</a>  
*/  
  
public class ChannelAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {  
private Context mContext;  
private List<ChannelBean> mList;  
private List<ChannelBean> recommendList; //推荐频道  
private List<ChannelBean> cityList; //地方新闻  
private int selectedSize;  
private int fixSize; //已选频道中固定频道大小  
private boolean isRecommend; //当前是否显示推荐频道  
private onItemRangeChangeListener onItemRangeChangeListener;  
private int mLeft, mRight; //蓝色线条距离屏幕左边的距离  
private int mTabY; //Tab距离parent的Y的距离  
  
public boolean isEdit = false;  
  
  
public ChannelAdapter(Context mContext, List<ChannelBean> mList, List<ChannelBean> recommendList, List<ChannelBean> cityList) {  
this.mContext = mContext;  
this.mList = mList;  
this.recommendList = recommendList;  
this.cityList = cityList;  
mLeft = -1;  
mRight = -1;  
}  
  
public void setOnItemRangeChangeListener(ChannelAdapter.onItemRangeChangeListener onItemRangeChangeListener) {  
this.onItemRangeChangeListener = onItemRangeChangeListener;  
}  
  
public int getSelectedSize() {  
return selectedSize;  
}  
  
public void setSelectedSize(int selectedSize) {  
this.selectedSize = selectedSize;  
}  
  
public int getFixSize() {  
return fixSize;  
}  
  
public void setFixSize(int fixSize) {  
this.fixSize = fixSize;  
}  
  
public void setRecommend(boolean recommend) {  
isRecommend = recommend;  
}  
  
@Override  
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {  
View view = LayoutInflater.from(mContext).inflate(viewType, parent, false);  
if (viewType == R.layout.adapter_channel_item) {  
return new ChannelHolder(view);  
} else if (viewType == R.layout.adapter_channel_item_hot_title) {  
return new HotTitleHolder(view);  
} else {  
return new TitleHolder(view);//adapter_channel_item_title  
}  
}  
  
@Override  
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {  
if (holder instanceof ChannelHolder) {  
setChannel((ChannelHolder) holder, mList.get(position));  
} else if (holder instanceof HotTitleHolder) {  
// setTab((HotTitleHolder) holder);  
} else {  
  
}  
}  
  
private void setChannel(final ChannelHolder holder, ChannelBean bean) {  
final int position = holder.getLayoutPosition();  
holder.name.setText(bean.getName());  
holder.name.setOnClickListener(new View.OnClickListener() {  
@Override  
public void onClick(View v) {  
if(isEdit) {  
if (holder.getLayoutPosition() < selectedSize + 1) {  
//tab上面的 点击移除  
if (holder.getLayoutPosition() > fixSize) {  
removeFromSelected(holder);  
}  
} else {  
//tab下面的 点击添加到已选频道  
selectedSize++;  
itemMove(holder.getLayoutPosition(), selectedSize);  
notifyItemChanged(selectedSize);  
if (onItemRangeChangeListener != null) {  
onItemRangeChangeListener.refreshItemDecoration();  
}  
}  
}  
}  
});  
holder.name.setOnLongClickListener(new View.OnLongClickListener() {  
@Override  
public boolean onLongClick(View v) {  
//返回true 防止长按拖拽事件跟点击事件冲突  
return true;  
}  
});  
holder.delete.setOnClickListener(new View.OnClickListener() {  
@Override  
public void onClick(View v) {  
removeFromSelected(holder);  
}  
});  
  
if(isEdit) {  
//tab下面的不显示删除按钮  
if (position - 1 < fixSize || position > selectedSize) {  
holder.delete.setVisibility(View.GONE);  
if(position > selectedSize){  
holder.add.setVisibility(View.VISIBLE);  
}  
} else {  
holder.delete.setVisibility(View.VISIBLE);  
holder.add.setVisibility(View.GONE);  
}  
  
  
}else{  
holder.add.setVisibility(View.GONE);  
holder.delete.setVisibility(View.GONE);  
}  
}  
  
  
private void removeFromSelected(ChannelHolder holder) {  
int position = holder.getLayoutPosition();  
holder.delete.setVisibility(View.GONE);  
ChannelBean bean = mList.get(position);  
if ((isRecommend && bean.isRecommend()) || (!isRecommend && !bean.isRecommend())) {  
//移除的频道属于当前tab显示的频道,直接调用系统的移除动画  
itemMove(position, selectedSize + 1);  
notifyItemRangeChanged(selectedSize + 1, 1);  
if (onItemRangeChangeListener != null) {  
//如果设置了itemDecoration,必须调用recyclerView.invalidateItemDecorations(),否则间距会不对  
onItemRangeChangeListener.refreshItemDecoration();  
}  
} else {  
//不属于当前tab显示的频道  
removeAnimation(holder.itemView, isRecommend ? mRight : mLeft, mTabY, position);  
}  
selectedSize--;  
}  
  
private void removeAnimation(final View view, final float x, final float y, final int position) {  
final int fromX = view.getLeft();  
final int fromY = view.getTop();  
final ObjectAnimator animatorX = ObjectAnimator.ofFloat(view, "translationX", 0, x - fromX);  
final ObjectAnimator animatorY = ObjectAnimator.ofFloat(view, "translationY", 0, y - fromY);  
ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 1, 0);  
final AnimatorSet set = new AnimatorSet();  
set.playTogether(animatorX, animatorY, alpha);  
set.setDuration(350);  
set.start();  
set.addListener(new Animator.AnimatorListener() {  
@Override  
public void onAnimationStart(Animator animation) {  
  
}  
  
@Override  
public void onAnimationEnd(Animator animation) {  
if (isRecommend) {  
cityList.add(0, mList.get(position));  
} else {  
recommendList.add(0, mList.get(position));  
}  
mList.remove(position);  
notifyItemRemoved(position);  
onItemRangeChangeListener.refreshItemDecoration();  
//这里需要重置view的属性  
resetView(view, x - fromX, y - fromY);  
}  
  
@Override  
public void onAnimationCancel(Animator animation) {  
  
}  
  
@Override  
public void onAnimationRepeat(Animator animation) {  
  
}  
});  
}  
  
/**  
* 重置view的位置  
*  
* @param view  
* @param toX  
* @param toY  
*/  
private void resetView(View view, float toX, float toY) {  
ObjectAnimator animatorX = ObjectAnimator.ofFloat(view, "translationX", -toX, 0);  
ObjectAnimator animatorY = ObjectAnimator.ofFloat(view, "translationY", -toY, 0);  
ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 0, 1);  
AnimatorSet set = new AnimatorSet();  
set.playTogether(animatorX, animatorY, alpha);  
set.setDuration(0);  
set.setStartDelay(5);  
set.start();  
}  
  
void itemMove(int fromPosition, int toPosition) {  
if (fromPosition < toPosition) {  
for (int i = fromPosition; i < toPosition; i++) {  
Collections.swap(mList, i, i + 1);  
}  
} else {  
for (int i = fromPosition; i > toPosition; i--) {  
Collections.swap(mList, i, i - 1);  
}  
}  
notifyItemMoved(fromPosition, toPosition);  
}  
  
@Override  
public int getItemCount() {  
return mList == null ? 0 : mList.size();  
}  
  
@Override  
public int getItemViewType(int position) {  
return mList.get(position).getLayoutId();  
}  
  
public class ChannelHolder extends RecyclerView.ViewHolder {  
public TextView name;  
public ImageView delete;  
  
public ImageView add;  
  
public ChannelHolder(View itemView) {  
super(itemView);  
name = itemView.findViewById(R.id.channelName);  
delete = itemView.findViewById(R.id.channelDelete);  
add = itemView.findViewById(R.id.channelAdd);  
}  
}  
  
class HotTitleHolder extends RecyclerView.ViewHolder {  
  
public HotTitleHolder(View itemView) {  
super(itemView);  
}  
}  
  
  
class TitleHolder extends RecyclerView.ViewHolder {  
  
public TitleHolder(View itemView) {  
super(itemView);  
}  
}  
  
public interface onItemRangeChangeListener {  
void refreshItemDecoration();  
}  
}  

ChannelItemDecoration:

 
  
import android.graphics.Rect;  
import android.view.View;  
  
import androidx.annotation.NonNull;  
import androidx.recyclerview.widget.RecyclerView;  
  
import com.xx.xx.R;  
  

public class ChannelItemDecoration extends RecyclerView.ItemDecoration {  
private int spanCount;  
private int spacing;  
private boolean includeEdge;  
private int tabPosition;  
  
public ChannelItemDecoration(int spanCount, int spacing, boolean includeEdge) {  
this.spanCount = spanCount;  
this.spacing = spacing;  
this.includeEdge = includeEdge;  
}  
  
@Override  
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, RecyclerView parent, @NonNull RecyclerView.State state) {  
int position = parent.getChildAdapterPosition(view);  
if (position > 0) {  
int id = parent.getAdapter().getItemViewType(position);  
if (id == R.layout.adapter_channel_item_hot_title) {  
tabPosition = position;  
}  
if (id == R.layout.adapter_channel_item) {  
if (position <= tabPosition) {  
position--;  
} else {  
position = position - (tabPosition + 1);  
}  
int column = position % spanCount; //列数  
if (includeEdge) {  
outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)  
outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)  
  
if (position < spanCount) { // top edge  
outRect.top = 20;  
}  
outRect.bottom = 20; // item bottom  
} else {  
outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing)  
outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f / spanCount) * spacing)  
if (position >= spanCount) {  
outRect.top = 20; // item top  
}  
}  
}  
}  
}  
}

ChannelItemDragCallback:



  
import static androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_DRAG;  
  
import android.graphics.Canvas;  
import android.graphics.Color;  
import android.graphics.DashPathEffect;  
import android.graphics.Paint;  
import android.graphics.PathEffect;  
import android.view.View;  
  
import androidx.annotation.NonNull;  
import androidx.recyclerview.widget.ItemTouchHelper;  
import androidx.recyclerview.widget.RecyclerView;  
  
  
public class ChannelItemDragCallback extends ItemTouchHelper.Callback {  
private static final String TAG = "ItemDragCallback";  
private ChannelAdapter mAdapter;  
private Paint mPaint; //虚线画笔  
private int mPadding; //虚线框框跟按钮间的距离  
  
public ChannelItemDragCallback(ChannelAdapter mAdapter, int mPadding) {  
this.mAdapter = mAdapter;  
this.mPadding = mPadding;  
mPaint = new Paint();  
mPaint.setColor(Color.GRAY);  
mPaint.setAntiAlias(true);  
mPaint.setStrokeWidth(1);  
mPaint.setStyle(Paint.Style.STROKE);  
PathEffect pathEffect = new DashPathEffect(new float[]{5f, 5f}, 5f); //虚线  
mPaint.setPathEffect(pathEffect);  
}  
  
@Override  
public int getMovementFlags(@NonNull RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {  
if(!mAdapter.isEdit) return 0;  
//固定位置及tab下面的channel不能拖动  
if (viewHolder.getLayoutPosition() < mAdapter.getFixSize() + 1 || viewHolder.getLayoutPosition() > mAdapter.getSelectedSize()) {  
return 0;  
}  
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;  
int swipeFlags = 0;  
return makeMovementFlags(dragFlags, swipeFlags);  
}  
  
@Override  
public boolean onMove(@NonNull RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {  
int fromPosition = viewHolder.getAdapterPosition(); //拖动的position  
int toPosition = target.getAdapterPosition(); //释放的position  
//固定位置及tab下面的channel不能拖动  
if (toPosition < mAdapter.getFixSize() + 1 || toPosition > mAdapter.getSelectedSize())  
return false;  
mAdapter.itemMove(fromPosition, toPosition);  
return true;  
}  
  
@Override  
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {  
  
}  
  
  
@Override  
public void onChildDrawOver(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {  
super.onChildDrawOver(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);  
if (dX != 0 && dY != 0 || isCurrentlyActive) {  
//长按拖拽时底部绘制一个虚线矩形  
c.drawRect(viewHolder.itemView.getLeft(),viewHolder.itemView.getTop()-mPadding,viewHolder.itemView.getRight(),viewHolder.itemView.getBottom(),mPaint);  
}  
}  
  
@Override  
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {  
super.onSelectedChanged(viewHolder, actionState);  
if(actionState==ACTION_STATE_DRAG){  
//长按时调用  
ChannelAdapter.ChannelHolder holder= (ChannelAdapter.ChannelHolder) viewHolder;  
//holder.name.setBackgroundColor(Color.parseColor("#FDFDFE"));  
holder.delete.setVisibility(View.GONE);  
holder.name.setElevation(5f);  
}  
}  
  
@Override  
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {  
super.clearView(recyclerView, viewHolder);  
ChannelAdapter.ChannelHolder holder= (ChannelAdapter.ChannelHolder) viewHolder;  
//holder.name.setBackgroundColor(Color.parseColor("#f0f0f0"));  
holder.name.setElevation(0f);  
holder.delete.setVisibility(View.VISIBLE);  
}  
}

ChannelActivity:

private fun initView() {  
  
val mList: MutableList<ChannelBean> = mutableListOf()  
  
val mineChannels = arrayOf(  
"娱乐",  
"军事",  
"文化",  
"视频",  
"股票",  
"动漫",  
"理财",  
"电竞",  
"数码",  
"星座",  
"教育",  
"美容",  
"旅游"  
)  
  
val hotChannels = arrayOf(  
"要闻",  
"体育",  
"新时代",  
"汽车",  
"时尚",  
"国际",  
"电影",  
"财经",  
"游戏",  
"科技",  
"房产",  
"政务",  
"图片",  
"独家"  
)  
  
// val manager = GridLayoutManager(this, 4)  
// manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {  
// override fun getSpanSize(position: Int): Int {  
// return mList[position].spanSize ?: 0  
// }  
// }  

//implementation 'com.google.android:flexbox:2.0.0'
val manager = FlexboxLayoutManager(this)  
manager.flexDirection = FlexDirection.ROW  
manager.justifyContent = JustifyContent.FLEX_START
  
binding.channelRv.layoutManager = manager  
  
//设置动画时间  
val animator = DefaultItemAnimator()  
animator.moveDuration = 300  
animator.removeDuration = 0  
binding.channelRv.itemAnimator = animator  
  
//我的频道  
val title = ChannelBean()  
title.layoutId = R.layout.adapter_channel_item_title  
title.spanSize = 4  
mList?.add(title)  
  
for (bean in mineChannels) {  
mList?.add(ChannelBean(bean, 1, R.layout.adapter_channel_item, true))  
}  
  
//热门频道  
val hotTitle = ChannelBean()  
hotTitle.layoutId = R.layout.adapter_channel_item_hot_title  
hotTitle.spanSize = 4  
mList?.add(hotTitle)  
  
val recommendList: MutableList<ChannelBean> = ArrayList()  
for (bean in hotChannels) {  
mList?.add(ChannelBean(bean, 1, R.layout.adapter_channel_item, true))  
}  
  
mAdapter = ChannelAdapter(this, mList, recommendList, listOf())  
mAdapter?.fixSize = 1  
mAdapter?.selectedSize = mineChannels.size  
mAdapter?.setRecommend(true)  
mAdapter?.setOnItemRangeChangeListener(this)  
  
binding.channelRv.adapter = mAdapter  
  
//val spacing: Int = (ScreenUtils.getAppScreenWidth() - SizeUtils.dp2px(70f) * 4) / 5  
binding.channelRv.addItemDecoration(  
ChannelItemDecoration(  
4,  
SizeUtils.dp2px(5f),  
false  
)  
)  
val callback =  
ChannelItemDragCallback(mAdapter, 2)  
val helper = ItemTouchHelper(callback)  
helper.attachToRecyclerView(binding.channelRv)  
}  
  
override fun refreshItemDecoration() {  
binding.channelRv.invalidateItemDecorations()  
}