列表自定义控件

平常软件开发中经常要用到自定义列表控件,难得有时间今天来分享一个常用的自定义列表控件。
话不多说,先上效果图:
自适应列表宽度列表
刚好最近有需求需要实现一个列宽能够随着内容变化的列表控件,在网上看了好多已经造好的轮子,最后觉得最下面这个轮子比较适合我的项目,当然得对这个项目进行一些改造。
主架构:左侧固定头部textview+右边可滑动头部分由横向LinearLayout实现,下方是一个recycleview,里面item布局左侧固定,右侧也由横向LinearLayout实现。好了,话不多说直接上代码。

package com.sc.basictable.weight;

import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.sc.basictable.BaseListAdapter;
import com.sc.basictable.R;

import java.util.ArrayList;

import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

/**
 * class description
 * 左侧固定,右侧可滑动的列表类
 * @author sc
 * @date 2021-04-29
 */
public class TableGroup extends LinearLayout {

    private String mHeadList[];
    //标题的宽度集合
    private int[] mHeadTextWidthList;
    private int mHeadHeight;
    private LinearLayout mRightTitleLayout;

    //展示数据时使用的RecycleView
    private RecyclerView mRecyclerView;
    private BaseListAdapter mAdapter;

    public TableGroup(Context context) {
        super(context);
        initData();
    }

    public TableGroup(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initData();
    }

    public TableGroup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initData();
    }

    private void initData(){
        setOrientation(VERTICAL);
        mHeadHeight = getResources().getDimensionPixelSize(R.dimen.dip40);
    }

    /**
     * 创建头部布局
     * @return
     */
    private View createHeadLayout(){
        LinearLayout headLayout = new LinearLayout(getContext());
        headLayout.setGravity(Gravity.CENTER);

        addHeaderTextView(mHeadList[0],mHeadTextWidthList[0],headLayout);

        mRightTitleLayout = new LinearLayout(getContext());
        for(int i=1;i<mHeadList.length;i++){
            addHeaderTextView(mHeadList[i],mHeadTextWidthList[i],mRightTitleLayout);
        }
        headLayout.addView(mRightTitleLayout);
        return headLayout;
    }

    //需要滑动的View集合
    private ArrayList<View> mMoveViewList = new ArrayList();
    /**
     * 创建数据展示布局
     * @return
     */
    private View createContentLayout(){
        RelativeLayout linearLayout = new RelativeLayout(getContext());
        mRecyclerView = new RecyclerView(getContext());
        LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        mRecyclerView.setLayoutManager(layoutManager);

        if(mAdapter != null){
            if(mAdapter instanceof BaseListAdapter) {
                mRecyclerView.setAdapter(mAdapter);
                mMoveViewList = mAdapter.getMoveViewList();
            }
        }

        linearLayout.addView(mRecyclerView, new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,
                RelativeLayout.LayoutParams.MATCH_PARENT));

        return linearLayout;
    }

    private void addHeaderTextView(String name,int width,LinearLayout parentView){
        TextView textView = new TextView(getContext());
        textView.setText(name);
        textView.setGravity(Gravity.CENTER);
        parentView.addView(textView,width,mHeadHeight);
    }

    //手指按下时的位置
    private float mStartX = 0;
    //滑动时和按下时的差值
    private int mMoveOffsetX = 0;
    //最大可滑动差值
    private int mFixX = 0;
    //触发拦截手势的最小值
    private int mTriggerMoveDis = 30;
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                mStartX = ev.getX();
                break;
                case MotionEvent.ACTION_MOVE:
                    int offsetX = (int) Math.abs(ev.getX() - mStartX);
                    if(offsetX > mTriggerMoveDis){//水平移动大于30触发拦截
                        return true;
                    }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                return true;
            case MotionEvent.ACTION_MOVE:
                int offSetX = (int) Math.abs(event.getX() - mStartX);
                if(offSetX > 30){
                    mMoveOffsetX = (int) (mStartX - event.getX() + mFixX);
                    if(0 > mMoveOffsetX){//如果滑到最左端,则不滑动
                        mMoveOffsetX = 0;
                    }else{
                        if((mRightTitleLayout.getWidth() + mMoveOffsetX)> rightTitleTotalWidth()){
                            mMoveOffsetX = rightTitleTotalWidth() - mRightTitleLayout.getWidth();
                        }
                    }
                    //跟随手指向右滚动
                    mRightTitleLayout.scrollTo(mMoveOffsetX, 0);
                    if (null != mMoveViewList) {
                        for (int i = 0; i < mMoveViewList.size(); i++) {
                            //使每个item随着手指向右滚动
                            mMoveViewList.get(i).scrollTo(mMoveOffsetX, 0);
                        }
                    }
                }

                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mFixX = mMoveOffsetX; //设置最大水平平移的宽度
                mAdapter.setFixX(mFixX); //列表得同步移动
                break;
        }

        return super.onTouchEvent(event);
    }

    //右边可滑动的总宽度
    private int mRightTotalWidth = 0;
    /**
     * 右边可滑动的总宽度
     * @return
     */
    private int rightTitleTotalWidth() {
        if (0 == mRightTotalWidth) {
            for (int i = 1; i < mHeadList.length; i++) {
                mRightTotalWidth = mRightTotalWidth + mHeadTextWidthList[i];
            }
        }
        return mRightTotalWidth;
    }

    /**
     * 设置表头,和表头每一栏的宽度
     * @param headers
     * @param headWidth
     */
    public void setHeaders(String headers[],int headWidth[]){
        mHeadList = headers;
        mHeadTextWidthList = headWidth;
    }

    /**
     * 添加视图
     * @param adapter
     */
    public void setAdapter(BaseListAdapter adapter){
        mAdapter = adapter;
        addView(createHeadLayout());
        addView(createContentLayout());
    }

}

这是基础的recycleview的适配器,具体的可以根据自己的需求实现定制。

package com.sc.basictable;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;

import java.util.ArrayList;
import java.util.List;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

/**
 * class description
 * 基础适配器
 * @author
 * @date
 */
public abstract class BaseListAdapter<T> extends RecyclerView.Adapter<BaseViewHolder> {

    private LayoutInflater mLayoutInflater;
    private List<T> mDataList;
    private int mLayoutId;
    private int mFixX;
    private ArrayList<View> mMoveViewList = new ArrayList<>();

    public BaseListAdapter(Context context,List<T> dataList,int layoutId){
        mLayoutInflater = LayoutInflater.from(context);
        mDataList = dataList;
        mLayoutId = layoutId;
    }

    @NonNull
    @Override
    public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View itemView = mLayoutInflater.inflate(mLayoutId,parent,false);
        BaseViewHolder holder = new BaseViewHolder(itemView);
        //获取列表右侧可滑动的部分
        LinearLayout moveLayout = holder.getView(R.id.id_move_layout);//由继承的子控件自定义
        setWidths(holder,moveLayout);//设置右侧滑动部分的宽度

        moveLayout.scrollTo(mFixX,0);//当有新的item创建出来的时候需要滑动到之前已经滑动到的位置
        mMoveViewList.add(moveLayout);
        return holder;
    }


    @Override
    public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
        bindData(holder, mDataList.get(position));
    }

    public abstract void bindData(BaseViewHolder holder, T data);

    public abstract void setWidths(BaseViewHolder holder, LinearLayout moveLayout);

    public ArrayList<View> getMoveViewList(){
        return mMoveViewList;
    }

    @Override
    public int getItemCount() {
        return mDataList == null?0:mDataList.size();
    }

    /**
     * 设置列表滚动的位置
     * @param fixX
     */
    public void setFixX(int fixX){
        mFixX=fixX;
    }
}

根据自己的需求实现提供的接口,点击事件与长按事件,当然也可自己添加实现。

package com.sc.basictable;

import android.util.SparseArray;
import android.view.View;
import android.widget.AdapterView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

/**
 * class description
 * 基础viewholder
 * @author
 * @date
 */
public class BaseViewHolder extends RecyclerView.ViewHolder implements View.OnLongClickListener, View.OnClickListener {

    private SparseArray<View> viewSparseArray;
    private onItemClickListener mOnItemClickListener;

    public BaseViewHolder(@NonNull View itemView) {
        super(itemView);
        itemView.setOnLongClickListener(this);
        itemView.setOnClickListener(this);
        viewSparseArray = new SparseArray<>();
    }
    /**
     * 根据 ID 来获取 View
     *
     * @param viewId viewID
     * @param <T>    泛型
     * @return 将结果强转为 View 或 View 的子类型
     */
    public <T extends View> T getView(int viewId) {
        // 先从缓存中找,找打的话则直接返回
        // 如果找不到则 findViewById ,再把结果存入缓存中
        View view = viewSparseArray.get(viewId);
        if (view == null) {
            view = itemView.findViewById(viewId);
            viewSparseArray.put(viewId, view);
        }
        return (T) view;
    }

    public BaseViewHolder setText(int viewId, CharSequence text) {
        TextView tv = getView(viewId);
        tv.setText(text);
        return this;
    }


    public interface onItemClickListener {

        void onItemClickListener(int position);

        void onItemLongClickListener(int position);

    }

    public void setOnItemClickListener(onItemClickListener listener){
        mOnItemClickListener = listener;
    }

    @Override
    public void onClick(View view) {
        if(mOnItemClickListener != null){
            mOnItemClickListener.onItemClickListener(getAdapterPosition());
        }
    }

    @Override
    public boolean onLongClick(View view) {
        if(mOnItemClickListener != null){
            mOnItemClickListener.onItemLongClickListener(getAdapterPosition());
        }
        return false;
    }
}

详细请看:源码地址

参考文章:https://www.jianshu.com/p/320c499da51d

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水的川

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值