Android自定义控件全览(一)

26 篇文章 0 订阅
26 篇文章 0 订阅

Android自定义控件总结(一)

目的:收集和整理所有的Android自定义控件
在这里插入图片描述



前言

后续会不断添加自定义控件实例,希望做成一个Android自定义控件大全
在这里插入图片描述


一、面包屑布局(BreadCrumbView)

1.自定义BreadCrumbView,继承FrameLayout

01.定义变量参数(需要重点关注TabListener),创建构造函数:

//自定义BreadCrumbsView,继承FrameLayout
public class BreadCrumbsView extends FrameLayout {
    private Context mContext;
    private LinkedList<Tab> tabList = new LinkedList<>();
    private RecyclerView recyclerView;
    private TabAdapter tabAdapter;
    private OnTabListener onTabListener;
    
    public BreadCrumbsView(Context context) {
        super(context);
        init(context);
    }

    public BreadCrumbsView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

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

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public BreadCrumbsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context);
    }
    ......

02.定义初始化函数,进行传参(init()函数需要放到构造函数之中):

    private void init(Context context) {
        this.mContext = context;
        View view = View.inflate(context, R.layout.layout_breadcrumbs_container, null);
        recyclerView = view.findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false));
        //设置每个item的TextView的最大宽度值
        float maxWidth = (ScreenUtil.getScreenWidth(mContext) - ScreenUtil.dip2px(mContext, 32)) / 3.0f - ScreenUtil.dip2px(mContext, 8);
        //为自定义View设置适配器,定义每个itemView的内容和样式
        //TabAdapter的构造器的参数:context、tabList、点击事件
        tabAdapter = new TabAdapter(mContext, tabList, new OnClickListener() {
            @Override
            public void onClick(View v) {
                int index = (int) v.getTag();
                //触发点击事件,自定义处理点击事件
                selectAt(index);
            }
        }, maxWidth);
        recyclerView.setAdapter(tabAdapter);
        addView(view);
    }

03.定义添加Tab的函数

    //为面包屑布局的每一个itemView设置一个tab,记录itemView的状态、内容等数值
    private void addTab(Tab tab) {
        // 修改tab的状态
        if (!tabList.isEmpty()) {
            tabList.getLast().setCurrent(false);
        }
        // 设置新增tab的数据及状态
        // 设置角标
        tab.setIndex(getCurrentIndex() + 1);
        // 设置状态
        tab.setCurrent(true);
        tabList.add(tab);
        if (onTabListener != null) {
            onTabListener.onAdded(tab);
        }
        tabAdapter.notifyDataSetChanged();
    }

    //添加新的面包屑
    public void addTab(String content, Map<String, String> value) {
        Tab tab = new Tab();
        tab.setContent(content);
        tab.setValue(value);
        addTab(tab);
    }

04.定义Tab的点击事件

    //非active状态的Tab支持选中事件,改变tabList的内容
    public void selectAt(int index) {
        // 判断角标越界,当前的tab不可点击
        if (!tabList.isEmpty() && tabList.size() > index && !tabList.get(index).isCurrent()) {
            // 移除后面的tab数据
            int size = tabList.size();
            // 从尾部开始删除
            for (int i = size - 1; i > index; i--) {
                if (onTabListener != null) {
                    onTabListener.onRemoved(tabList.getLast());
                }
                tabList.removeLast();
            }
            // 修改当前选中的tab状态
            tabList.getLast().setCurrent(true);
            if (onTabListener != null) {
                onTabListener.onActivated(tabList.getLast());
            }
            tabAdapter.notifyDataSetChanged();
        }
    }

    public int getCurrentIndex() {
        return tabList.size() - 1;
    }

    public void setOnTabListener(OnTabListener onTabListener) {
        this.onTabListener = onTabListener;
    }

    public Tab getLastTab() {
        if (!tabList.isEmpty()) {
            return tabList.getLast();
        }
        return null;
    }

05.设置itemView的Tab属性,存储列表中itemView的位置(是否最后一个)、内容等属性

    public static class Tab {

        private Context mContext;
        /**
         * 是否当前tab,非当前tab才可点击
         */
        private boolean current = false;
        /**
         * 角标,从0开始
         */
        private int index;
        /**
         * 面包屑内容
         */
        private String content;

        /**
         * 业务携带的参数
         */
        private Map<String, String> value;

        public boolean isCurrent() {
            return current;
        }

        public void setCurrent(boolean current) {
            this.current = current;
        }

        public int getIndex() {
            return index;
        }

        public void setIndex(int index) {
            this.index = index;
        }

        public String getContent() {
            return content;
        }

        public void setContent(String content) {
            this.content = content;
        }

        public Map<String, String> getValue() {
            return value;
        }

        public void setValue(Map<String, String> value) {
            this.value = value;
        }
    }

06.创建TabAdapter,设置itemView的内容和样式

    //自定义列表的适配器,设置itemView的内容和样式
    private class TabAdapter extends RecyclerView.Adapter<TabAdapter.ViewHolder> {
        private Context mContext;
        private List<Tab> list;
        private OnClickListener onItemClickListener;
        private float maxItemWidth;

        public TabAdapter(Context mContext, List<Tab> list, OnClickListener onItemClickListener, float maxItemWidth) {
            this.mContext = mContext;
            this.list = list;
            this.onItemClickListener = onItemClickListener;
            this.maxItemWidth = maxItemWidth;
        }

        @NonNull
        @Override
        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            View v = LayoutInflater.from(mContext).inflate(R.layout.layout_breadcrumbs_tab, parent, false);
            return new ViewHolder(v);
        }

        @Override
        public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
            Tab item = list.get(position);
            holder.tv.setText(item.getContent());
            if (item.isCurrent()) {
                holder.tv.setTextColor(Color.parseColor("#0B1D32"));
                holder.iv.setVisibility(GONE);
                holder.itemView.setOnClickListener(null);
            } else {
                holder.tv.setTextColor(Color.parseColor("#485666"));
                holder.iv.setVisibility(VISIBLE);
                //在onBindViewHolder()中设置点击事件,传递点击事件的回调(点击的位置)
                holder.itemView.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (onItemClickListener != null) {
                            v.setTag(position);
                            onItemClickListener.onClick(v);
                        }
                    }
                });
            }
            if (maxItemWidth > 0) {
                int width;
                if (item.isCurrent()) {
                    width = (int) maxItemWidth;
                } else {
                    width = (int) maxItemWidth - ScreenUtil.dip2px(mContext, 24);
                }
                holder.tv.setMaxWidth(width);
            }
        }

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

        class ViewHolder extends RecyclerView.ViewHolder {
            private TextView tv;
            private ImageView iv;

            public ViewHolder(View itemView) {
                super(itemView);
                tv = itemView.findViewById(R.id.tv_content);
                iv = itemView.findViewById(R.id.iv_arrow);
            }
        }
    }

07.自定义Tab点击事件,也就是BreadCrumbsView的itemView的点击事件回调

    public interface OnTabListener {
        /**
         * 新添加进来的回调
         *
         * @param tab
         */
        void onAdded(Tab tab);

        /**
         * 再次显示到栈顶的回调
         *
         * @param tab
         */
        void onActivated(Tab tab);

        /**
         * 被移除的回调
         *
         * @param tab
         */
        void onRemoved(Tab tab);
    }

2.使用自定义BreadCrumbView

01.设置BreadCrumbs的点击监听事件,获取点击的位置,自定义监听事件的回调方法

public class MainActivity extends AppCompatActivity {

    private BreadCrumbsView breadCrumbsView;

    LinkedList<Fragment> fragments = new LinkedList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        breadCrumbsView = findViewById(R.id.breadCrumbs);
        breadCrumbsView.setOnTabListener(new BreadCrumbsView.OnTabListener() {
            @Override
            public void onAdded(BreadCrumbsView.Tab tab) {
                Log.e("BreadCrumbsView", "BreadCrumbsView.OnTabListener#onAdded tab:" + tab.getIndex());
                addFragment(tab);
            }

            @Override
            public void onActivated(BreadCrumbsView.Tab tab) {
                Log.e("BreadCrumbsView", "BreadCrumbsView.OnTabListener#onActivated tab:" + tab.getIndex());
            }

            @Override
            public void onRemoved(BreadCrumbsView.Tab tab) {
                Log.e("BreadCrumbsView", "BreadCrumbsView.OnTabListener#onRemoved tab:" + tab.getIndex());
                removeLastFragment();
            }
        });
        Button btnAdd = findViewById(R.id.btn_add);
        btnAdd.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                StringBuilder sb = new StringBuilder("Tab");
                sb.append(breadCrumbsView.getCurrentIndex()+1);
             
                breadCrumbsView.addTab(sb.toString(), map);
            }
        });
    }

02.创建底部fragment,实现页面的切换

    private void addFragment(BreadCrumbsView.Tab tab) {
        Fragment fragment = BlankFragment.newInstance(String.format("我是第%d个Fragment", tab.getIndex()), "" + tab.getIndex());
        getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.container, fragment, String.valueOf(tab.getIndex()))
                .show(fragment)
                .addToBackStack(null)
                .commit();
        fragments.add(fragment);
    }
   
    //触发点击事件,每次删除一个Tab,就会对应删除一个Fragment。从尾部开始删除,知道点击事件触发的位置
    private void removeLastFragment() {
        if (fragments != null && fragments.size() > 1) {
            getSupportFragmentManager().popBackStackImmediate();
            fragments.removeLast();
            FragmentManager fragmentManager = getSupportFragmentManager();
            fragmentManager.beginTransaction()
                    .show(fragments.getLast())
                    .commit();
            fragmentManager.executePendingTransactions();
        }
    }

03.Activity后退按钮的点击事件,直接触发末位Tab点击事件

    @Override
    public void onBackPressed() {
        if (fragments != null && fragments.size() > 1) {
            breadCrumbsView.selectAt(fragments.size() - 1 - 1);
        } else {
            AlertDialog dialog = new AlertDialog.Builder(this)
                    .setTitle("提示")
                    .setMessage("确认退出么?")
                    .setCancelable(true)
                    .setPositiveButton("ok", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            finish();
                        }
                    }).create();
            dialog.show();
        }
    }

3.使用效果

在这里插入图片描述

4.项目目录

在这里插入图片描述


二、树型结构布局(TreeListView)

1.创建自定义适配器TreeAdapter

01.创建自定义列表的每一条itemView的数据集合:

public class TreePoint {
    private String ID;        //账号id
    private String NNAME;     //节点内容
    private String PARENTID;  //0表示父节点
    private String ISLEAF;    //1为叶子节点
    private int DISPLAY_ORDER; // 1   //同一个级别的显示顺序
    private boolean isExpand = false;  //是否展开了
    private boolean isSelected = false; //是否选中了
    ...省略了get/set方法

02.自定义TreeAdapter的构造函数:

public class TreeAdapter extends BaseAdapter {
    private Context mcontext;
    private List<TreePoint> pointList;
    private String keyword = "";  //搜索输入的keyword
    private HashMap<String, TreePoint> pointMap = new HashMap<>();
    
    //两种操作模式:1表示点击 或者 2表示选择
    private int operateMode = 1;
    
    //设置列表item的样式
    class ViewHolder {
        TextView text;
        ImageView icon;
        ImageButton ib_select;
    }
    
    public TreeAdapter(final Context mcontext, List<TreePoint> pointList, HashMap<String, TreePoint> pointMap) {
        this.mcontext = mcontext;   //上下文
        this.pointList = pointList;  //列表数据
        this.pointMap = pointMap;  //TreePoint和PARENTID的映射
    }

03.设置搜索关键字时列表的样式

    /**
     * 搜索的时候,先关闭所有的条目,然后,按照条件,找到含有关键字的数据
     * 如果是叶子节点,
     */
    public void setKeyword(String keyword) {
        this.keyword = keyword;
        for (TreePoint treePoint : pointList) {
            treePoint.setExpand(false);
        }
        if (!keyword.isEmpty()) {
            for (TreePoint tempTreePoint : pointList) {
                if (tempTreePoint.getNNAME().contains(keyword)) {
                    tempTreePoint.setExpand(true);
                    //展开从最顶层到该点的所有节点
                    openExpand(tempTreePoint);
                }
            }
        }
        this.notifyDataSetChanged();
    }

    /**
     * 从TreePoint开始一直展开到顶部
     * @param treePoint
     */
    private void openExpand(TreePoint treePoint) {
        if ("0".equals(treePoint.getPARENTID())) {
            treePoint.setExpand(true);
        } else {
            pointMap.get(treePoint.getPARENTID()).setExpand(true);
            openExpand(pointMap.get(treePoint.getPARENTID()));
        }
    }

04.计算Item的数量,这里直接返回pointList.size()会出现列表数据错误

    //准确计算数量
    @Override
    public int getCount() {
        int count = 0;
        for (TreePoint tempPoint : pointList) {
            if ("0".equals(tempPoint.getPARENTID())) {
                count++;
            } else {
                if (getItemIsExpand(tempPoint.getPARENTID())) {
                    count++;
                }
            }
        }
        return count;
    }

    //判断当前Id的tempPoint是否展开了
    private boolean getItemIsExpand(String ID) {
        for (TreePoint tempPoint : pointList) {
            if (ID.equals(tempPoint.getID())) {
                return tempPoint.isExpand();
            }
        }
        return false;
    }

05.获取列表position对应的item,此处需要对position进行转换,否则会出错

    @Override
    public Object getItem(int position) {
        return pointList.get(convertPostion(position));
    }

    private int convertPostion(int position) {
        int count = 0;
        for (int i = 0; i < pointList.size(); i++) {
            TreePoint treePoint = pointList.get(i);
            if ("0".equals(treePoint.getPARENTID())) {
                count++;
            } else {
                if (getItemIsExpand(treePoint.getPARENTID())) {
                    count++;
                }
            }
            if (position == (count - 1)) {
                return i;
            }
        }
        return 0;
    }

06.显示折叠布局,针对不同level的节点进行样式的设置

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if (convertView == null) {
            convertView = LayoutInflater.from(mcontext).inflate(R.layout.adapter_treeview, null);
            holder = new ViewHolder();
            holder.text = (TextView) convertView.findViewById(R.id.text);
            holder.icon = (ImageView) convertView.findViewById(R.id.icon);
            holder.ib_select = (ImageButton) convertView.findViewById(R.id.ib_select);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        final TreePoint tempPoint = (TreePoint) getItem(position);
        int level = TreeUtils.getLevel(tempPoint, pointMap);
        holder.icon.setPadding(25 * level, holder.icon.getPaddingTop(), 0, holder.icon.getPaddingBottom());
        if ("0".equals(tempPoint.getISLEAF())) {  //如果为父节点
            if (!tempPoint.isExpand()) {    //未展开显示加号
                holder.icon.setVisibility(View.VISIBLE);
                holder.icon.setImageResource(R.drawable.outline_list_collapse);
            } else {                        //展开了显示减号
                holder.icon.setVisibility(View.VISIBLE);
                holder.icon.setImageResource(R.drawable.outline_list_expand);
            }
        } else {   //如果叶子节点,没有前面的加号
            holder.icon.setVisibility(View.INVISIBLE);
        }
        if (operateMode == 1) {
            holder.ib_select.setVisibility(View.VISIBLE);
            holder.ib_select.setSelected(tempPoint.isSelected());
            holder.ib_select.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    onModeSelect(tempPoint);
                    //打印TreePoint的D
                    Toast.makeText(mcontext, "我点击的pointList的下标是:" + pointList.indexOf(tempPoint), Toast.LENGTH_SHORT).show();

                    //打印TreePoint的D
                    Toast.makeText(mcontext, "我点击的列表的位置是:" + position, Toast.LENGTH_SHORT).show();
                }
            });
        } else {
            holder.ib_select.setVisibility(View.GONE);
        }
        //如果存在搜索关键字
        if (keyword != null && !"".equals(keyword) && tempPoint.getNNAME().contains(keyword)) {
            int index = tempPoint.getNNAME().indexOf(keyword);
            int len = keyword.length();
            Spanned temp = Html.fromHtml(tempPoint.getNNAME().substring(0, index)
                    + "<font color=#FF0000>"    //字体变成红颜色
                    + tempPoint.getNNAME().substring(index, index + len) + "</font>"
                    + tempPoint.getNNAME().substring(index + len, tempPoint.getNNAME().length()));

            holder.text.setText(temp);
        } else {
            holder.text.setText(tempPoint.getNNAME());
        }
        holder.text.setCompoundDrawablePadding(DensityUtil.dip2px(mcontext, 10));
        return convertView;
    }

07.item的点击事件

    public void onItemClick(int position) {
        TreePoint treePoint = (TreePoint) getItem(position);
        if ("1".equals(treePoint.getISLEAF())) {   //点击叶子节点
            //处理回填
            Toast.makeText(mcontext, getSubmitResult(treePoint), Toast.LENGTH_SHORT).show();
            //打印TreePoint的ID
            Toast.makeText(mcontext, "我自己的ID是:" + treePoint.getID(), Toast.LENGTH_SHORT).show();
            //打印TreePoint的ID
            Toast.makeText(mcontext, "我的ParentID是:" + treePoint.getPARENTID(), Toast.LENGTH_SHORT).show();
            //打印TreePoint的D
            Toast.makeText(mcontext, "我点击的列表的位置是:" + position, Toast.LENGTH_SHORT).show();
        } else {  //如果点击的是父类
            if (treePoint.isExpand()) {
                for (TreePoint tempPoint : pointList) {
                    if (tempPoint.getPARENTID().equals(treePoint.getID())) {
                        if ("0".equals(treePoint.getISLEAF())) {
                            tempPoint.setExpand(false);
                        }
                    }
                }
                treePoint.setExpand(false);
            } else {
                treePoint.setExpand(true);
            }
        }
        this.notifyDataSetChanged();
    }

08.节点的选择按钮点击事件

    //选择操作
    private void onModeSelect(TreePoint treePoint) {
        if ("1".equals(treePoint.getISLEAF())) {   //如果点击的是叶子节点的选择按钮
            //点击回调,treePoint默认是未选中,点击事件之后treePoint是选中了
            treePoint.setSelected(!treePoint.isSelected());
        } else {                                   //如果点击的是父节点的选中按钮,需要把下级的子节点全部选中
            int position = pointList.indexOf(treePoint);
            boolean isSelect = treePoint.isSelected();
            treePoint.setSelected(!isSelect);
//            if(position == -1){
//                return ;
//            }
//            if(position == pointList.size()-1){
//                return;
//            }
            position++;
            for (; position < pointList.size(); position++) {
                TreePoint tempPoint = pointList.get(position);
                if (tempPoint.getPARENTID().equals(treePoint.getPARENTID())) { //如果找到和自己同级的数据就返回,和自己同级的数据的上面的数据都是自己的下级
                    break;
                }
                tempPoint.setSelected(!isSelect);
            }
        }
        this.notifyDataSetChanged();
    }

    //选中所有的treePoint
    public void selectAllPoint() {
        for (TreePoint tempPoint : pointList) {
            if (!tempPoint.isSelected()) {
                tempPoint.setSelected(true);
                this.notifyDataSetChanged();
            }
        }
    }

    //取消选中所有的treePoint
    public void unSelectAllPoint() {
        for (TreePoint tempPoint : pointList) {
            tempPoint.setSelected(false);
            this.notifyDataSetChanged();
        }
    }

09.显示节点的路径

    private String getSubmitResult(TreePoint treePoint) {
        StringBuilder sb = new StringBuilder();
        addResult(treePoint, sb);
        String result = sb.toString();
        if (result.endsWith("-")) {
            result = result.substring(0, result.length() - 1);
        }
        return result;
    }

    private void addResult(TreePoint treePoint, StringBuilder sb) {
        if (treePoint != null && sb != null) {
            sb.insert(0, treePoint.getNNAME() + "-");
            if (!"0".equals(treePoint.getPARENTID())) {
                addResult(pointMap.get(treePoint.getPARENTID()), sb);
            }
        }
    }

2.使用自定义TreeAdapter

01.为树型布局赋值

    //初始化数据
    //数据特点:TreePoint 之间的关系特点   id是任意唯一的。    如果为根节点 PARENTID  为"0"   如果没有子节点,也就是本身是叶子节点的时候ISLEAF = "1"
    //  DISPLAY_ORDER 是同一级中 显示的顺序
    //如果需要做选中 单选或者多选,只需要给TreePoint增加一个选中的属性,在ReasonAdapter中处理就好了
    private void initData() {
        pointList.clear();
        int id =1000;
        int parentId = 0;
        int parentId2 = 0;
        int parentId3 = 0;
        for(int i=1;i<3;i++){
            id++;
            pointList.add(new TreePoint(""+id,"分类"+i,"" + parentId,"0",i));
            for(int j=1;j<3;j++){
                if(j==1){
                    parentId2 = id;
                }
                id++;
                pointList.add(new TreePoint(""+id,"分类"+i+"_"+j,""+parentId2,"0",j));
                for(int k=1;k<3;k++){
                    if(k==1){
                         parentId3 = id;
                    }
                    id++;
                    pointList.add(new TreePoint(""+id,"分类"+i+"_"+j+"_"+k,""+parentId3,"1",k));
                }
            }
        }
        //打乱集合中的数据
        Collections.shuffle(pointList);
        //对集合中的数据重新排序
        updateData();
    }

02.对赋值的数据进行排序

    //对数据排序 深度优先
    private void updateData() {
        for (TreePoint treePoint : pointList) {
            //pointMap是treePoint的ID和treePoint的映射
            pointMap.put(treePoint.getID(), treePoint);
        }
        Collections.sort(pointList, new Comparator<TreePoint>() {
            @Override
            public int compare(TreePoint lhs, TreePoint rhs) {
                int llevel = TreeUtils.getLevel(lhs, pointMap);
                int rlevel = TreeUtils.getLevel(rhs, pointMap);
                if (llevel == rlevel) {
                    if (lhs.getPARENTID().equals(rhs.getPARENTID())) {  //左边小
                        return lhs.getDISPLAY_ORDER() > rhs.getDISPLAY_ORDER() ? 1 : -1;
                    } else {  //如果父辈id不相等
                        //同一级别,不同父辈
                        TreePoint ltreePoint = TreeUtils.getTreePoint(lhs.getPARENTID(), pointMap);
                        TreePoint rtreePoint = TreeUtils.getTreePoint(rhs.getPARENTID(), pointMap);
                        return compare(ltreePoint, rtreePoint);  //父辈
                    }
                } else {  //不同级别
                    if (llevel > rlevel) {   //左边级别大       左边小
                        if (lhs.getPARENTID().equals(rhs.getID())) {
                            return 1;
                        } else {
                            TreePoint lreasonTreePoint = TreeUtils.getTreePoint(lhs.getPARENTID(), pointMap);
                            return compare(lreasonTreePoint, rhs);
                        }
                    } else {   //右边级别大   右边小
                        if (rhs.getPARENTID().equals(lhs.getID())) {
                            return -1;
                        }
                        TreePoint rreasonTreePoint = TreeUtils.getTreePoint(rhs.getPARENTID(), pointMap);
                        return compare(lhs, rreasonTreePoint);
                    }
                }
            }
        });
        adapter.notifyDataSetChanged();
    }


二、分页布局(jetpack的paging库)

1.定义适配器、数据接口Service、数据源

01.首先当然是导入依赖

    def paging_version = "3.0.0-alpha12"
    implementation "androidx.paging:paging-runtime:$paging_version"

02.定义适配器:

class MyAdapter(private val context: Context) : PagingDataAdapter<User, MyAdapter.ViewHolder>(Comparator) {

    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var tvName: TextView? = null
        init {
            tvName = itemView.findViewById(R.id.tvName)
        }

        fun bind(item: User?) {
            tvName?.text = item?.name.toString()
        }
    }

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): UserViewHolder {
        val view =
            LayoutInflater.from(parent.context).inflate(R.layout.adapter_user_item, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = getItem(position)
        // Note that item may be null. ViewHolder must support binding a
        // null item as a placeholder.
        holder.bind(item)
        holder.itemView.setOnClickListener {
            Toast.makeText(context, "haha", Toast.LENGTH_SHORT).show()

        }

    }

    object Comparator : DiffUtil.ItemCallback<User>() {
        override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
            // Id is unique.
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
            return oldItem == newItem
        }
    }

}

03.定义API接口请求数据,模拟耗时操作

class ExampleBackendService {
    private var count = 0
    suspend fun searchUsers(): Response {
        Log.d("duo_shine", "发起请求 模拟耗时操作")
        return withContext(Dispatchers.IO) {
            //模拟耗时请求的操作
            delay(2000)
            val data = ArrayList<User>()
            for(i in count until count + 20){
                data.add(User(i, i))
            }
            count +=20
            Response(data, count)
        }
    }
}

04.定义数据源:

class ExamplePagingSource(private val backend: ExampleBackendService) : PagingSource<Int, User>() {

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, User> {
        return try {
            //从后台或数据库异步获取数据
            val response = backend.searchUsers()
            val nextKey = response.nextKey
            //组成成功
            LoadResult.Page(
                data = response.response,
                prevKey = null,
                nextKey = if(nextKey!! >= 100) null else nextKey
            )
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }
}

2.使用Paging分页布局

01.设置自定义适配器,实现分页请求数据的效果

class MainActivity : AppCompatActivity() {

    private val flow = Pager(PagingConfig(pageSize = 1)) {
        ExamplePagingSource(ExampleBackendService())
    }.flow

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
        recyclerView.layoutManager = LinearLayoutManager(this)
        val adapter = UserAdapter(this)
        recyclerView.adapter = adapter
        lifecycleScope.launch {
            flow.collectLatest {   
                adapter.submitData(it)  //使用submitData()方法直接提交数据到适配器中
            }
        }
    }
}

三、自定义底部导航栏(BottomNavigationView)

创建自定义工具类NavHelper

01.NavHelper用于解决页面中Fragment的调度与重用问题

public class NavHelper<T> {
    // 所有的Tab集合
    private final SparseArray<Tab<T>> tabs = new SparseArray<>();

    // 用于初始化的必须参数
    private final Context context;
    private final int containerId;
    private final FragmentManager fragmentManager;
    private final OnTabChangedListener<T> listener;

    // 当前的一个选中的Tab
    private Tab<T> currentTab;

    public NavHelper(Context context, int containerId,
                     FragmentManager fragmentManager,
                     OnTabChangedListener<T> listener) {
        this.context = context;
        this.containerId = containerId;
        this.fragmentManager = fragmentManager;
        this.listener = listener;
    }

02.NavHelper中处理BottomNavigationView的点击事件:

    /**
     * 添加Tab
     *
     * @param menuId Tab对应的菜单Id
     * @param tab    Tab
     */
    public NavHelper<T> add(int menuId, Tab<T> tab) {
        tabs.put(menuId, tab);
        return this;
    }

    /**
     * 获取当前的显示的Tab
     *
     * @return 当前的Tab
     */
    public Tab<T> getCurrentTab() {
        return currentTab;
    }

    /**
     * 执行点击菜单的操作
     *
     * @param menuId 菜单的Id
     * @return 是否能够处理这个点击
     */
    public boolean performClickMenu(int menuId) {
        // 集合中寻找点击的菜单对应的Tab,
        // 如果有则进行处理
        Tab<T> tab = tabs.get(menuId);
        if (tab != null) {
            doSelect(tab);
            return true;
        }

        //默认返回的false,处理成功返回的才是true
        return false;
    }

    /**
     * 进行Tab选择操作
     *
     * @param tab Tab
     */
    private void doSelect(Tab<T> tab) {
        //首先将之前选中的tab置为空
        Tab<T> oldTab = null;

        if (currentTab != null) {
            oldTab = currentTab;
            if (oldTab == tab) {
                // 如果说当前的Tab就是点击的Tab,那么我们不做处理
                return;
            }
        }
        // 赋值并调用切换方法
        currentTab = tab;
        doTabChanged(currentTab, oldTab);

    }

    /**
     * 进行Fragment的调度操作
     *
     * @param newTab 新的
     * @param oldTab 旧的
     */
    private void doTabChanged(Tab<T> newTab, Tab<T> oldTab) {
        FragmentTransaction ft = fragmentManager.beginTransaction();

        if (oldTab != null) {
            if (oldTab.fragment != null) {
                // 从界面移除,但是还在Fragment的缓存空间中
                ft.detach(oldTab.fragment);
            }
        }

        if (newTab != null) {
            if (newTab.fragment == null) {
                // 首次新建,直接从Tab的clx参数中将fragment创建出来
                Fragment fragment = fragmentManager.getFragmentFactory().instantiate(context.getClassLoader(), newTab.clx.getName());
                // 缓存起来
                newTab.fragment = fragment;
                // 提交到FragmentManger
                ft.add(containerId, fragment, newTab.clx.getName());
            } else {
                // 从FragmentManger的缓存空间中重新加载到界面中
                ft.attach(newTab.fragment);
            }
        }
        // 提交事务
        ft.commit();
        // 通知回调
        notifyTabSelect(newTab, oldTab);
    }

    /**
     * 回调我们的监听器
     *
     * @param newTab 新的Tab<T>
     * @param oldTab 旧的Tab<T>
     */
    private void notifyTabSelect(Tab<T> newTab, Tab<T> oldTab) {
        if (listener != null) {
            listener.onTabChanged(newTab, oldTab);
        }
    }

03.NavHelper中定义Tab这个封装类:

    /**
     * 所有的Tab基础属性,
     * arg1:fragment的class信息,用于后面利用context.getClassLoader()获取这个fragment
     * arg2:extra则是fragment对应的额外信息,用于回调到MainActivity之中
     * @param <T> 范型的额外参数
     */
    public static class Tab<T> {
        public Tab(Class<?> clx, T extra) {
            this.clx = clx;
            this.extra = extra;
        }

        // Fragment对应的Class信息
        public Class<?> clx;
        // 额外的字段,用户自己设定需要使用
        public T extra;

        // 内部缓存的对应的Fragment,
        // Package权限,外部无法使用
        Fragment fragment;
    }

    /**
     * 定义事件处理完成后的回调接口
     */
    public interface OnTabChangedListener<T> {
        void onTabChanged(Tab<T> newTab, Tab<T> oldTab);
    }

使用自定义适配器

01.在BottomNavigationView的点击监听事件中将事件流转接到工具类中

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mNavigation = findViewById(R.id.navigation);
        mTitle = findViewById(R.id.txt_title);
        mAction = findViewById(R.id.btn_action);

        // 初始化底部辅助工具类
        mNavHelper = new NavHelper<>(this, R.id.lay_container, getSupportFragmentManager(), this);
        mNavHelper.add(R.id.action_home, new NavHelper.Tab<>(ActiveFragment.class, R.string.title_home))
                .add(R.id.action_group, new NavHelper.Tab<>(GroupFragment.class, R.string.title_group))
                .add(R.id.action_contact, new NavHelper.Tab<>(ContactFragment.class, R.string.title_contact));

        // 添加对底部按钮点击的监听
        mNavigation.setOnNavigationItemSelectedListener(this);

        // 从底部导中接管我们的Menu,然后进行手动的触发第一次点击
        Menu menu = mNavigation.getMenu();
        // 触发首次选中Home
        menu.performIdentifierAction(R.id.action_home, 0);

    }

    /**
     * 当我们的底部导航被点击的时候触发
     *
     * @param item MenuItem
     * @return True 代表我们能够处理这个点击
     */
    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        // 转接事件流到工具类中
        return mNavHelper.performClickMenu(item.getItemId());
    }

02.在MainActivity中处理工具类回调给我们的监听器

    /**
     * 回调我们的监听器,可以进行页面逻辑的切换操作
     *
     * @param newTab 新的Tab<T>
     * @param oldTab 旧的Tab<T>
     */
    private void notifyTabSelect(Tab<T> newTab, Tab<T> oldTab) {
        if (listener != null) {
            listener.onTabChanged(newTab, oldTab);
        }
    }

    /**
     * 所有的Tab基础属性
     *
     * @param <T> 范型的额外参数
     */
    public static class Tab<T> {
        public Tab(Class<?> clx, T extra) {
            this.clx = clx;
            this.extra = extra;
        }

        // Fragment对应的Class信息
        public Class<?> clx;
        // 额外的字段,用户自己设定需要使用
        public T extra;

        // 内部缓存的对应的Fragment,
        // Package权限,外部无法使用
        Fragment fragment;
    }

    /**
     * 定义事件处理完成后的回调接口
     */
    public interface OnTabChangedListener<T> {
        void onTabChanged(Tab<T> newTab, Tab<T> oldTab);
    }

四、自定义RecyclerView(单选、多选、长按删除)

01.列表样式的核心就是适配器

01.定义适配器的构造函数和初始化列表数据的选中状态:

//自定义适配器,继承RecyclerView.Adapter
public class RecyViewAdapter extends RecyclerView.Adapter<RecyViewAdapter.ViewHolder> {

    private Context mContext;
    private List<String>list;
    private HashMap<Integer, Boolean> maps = new HashMap<Integer, Boolean>();//保存列表中每个item的选中状态
    public RecyclerViewOnItemClickListener onItemClickListener;

    public RecyViewAdapter(Context mContext, List<String> list) {
        this.mContext = mContext;
        this.list = list;
        initMap();
    }

    //设置每次进入列表,item都是未选中的状态
    private void initMap() {
        for (int i = 0; i <list.size() ; i++) {
            maps.put(i,false);
        }
    }

02.覆写父类的三个重要方法:

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        View itemView = LayoutInflater.from(mContext).inflate(R.layout.item_provice_chose, viewGroup, false);
        return new ViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder viewHolder, @SuppressLint("RecyclerView") final int i) {
        viewHolder.province_name.setText(list.get(i));
        viewHolder.cbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                maps.put(i, isChecked);
            }
        });
        viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                maps.put(i, true);
                if (onItemClickListener != null) {
                    onItemClickListener.onItemLongClick(view, i);
                }
            }
        });
        if (maps.get(i) == null) {
            maps.put(i, false);
        }
        //没有设置tag之前会有item重复选框出现,设置tag之后,此问题解决
        viewHolder.cbox.setChecked(maps.get(i));
    }

    @Override
    public int getItemCount() {
        return list.size();
    }

03.创建自定义方法,全选、反选、多选和删除:

    //获取最终的map存储数据
    public Map<Integer, Boolean> getMap() {
        return maps;
    }
    
    //全选方法
    public void All() {
        Set<Map.Entry<Integer, Boolean>> entries = maps.entrySet();
        boolean shouldall = false;
        for (Map.Entry<Integer, Boolean> entry : entries) {
            Boolean value = entry.getValue();
            if (!value) {
                shouldall = true;
                break;
            }
        }
        for (Map.Entry<Integer, Boolean> entry : entries) {
            entry.setValue(shouldall);
        }
        notifyDataSetChanged();
    }

    //反选
    public void neverall() {
        Set<Map.Entry<Integer, Boolean>> entries = maps.entrySet();
        for (Map.Entry<Integer, Boolean> entry : entries) {
            entry.setValue(!entry.getValue());
        }
        notifyDataSetChanged();
    }

    //多选
    public void MultiSelection(int position) {
        //对当前状态取反
        if (maps.get(position)) {
            maps.put(position, false);
        } else {
            maps.put(position, true);
        }
        notifyItemChanged(position);
    }
    
    //删除数据
    public void delete(int position) {
        list.remove(position);
        notifyItemRemoved(position);
        //此处是重点,解决了删除数据后列表错位的问题
        notifyItemRangeChanged(position, list.size() - 1);
    }

04.设置自定义ViewHolder以及监听事件的回调:

    public class ViewHolder extends RecyclerView.ViewHolder{
        private RecyclerViewOnItemClickListener mListener;//接口
        private CheckBox cbox;
        private TextView province_name;

        public ViewHolder(View itemView) {
            super(itemView);
            cbox = itemView.findViewById(R.id.cbox);
            province_name = itemView.findViewById(R.id.province_name);
        }
    }

    //回调的接口
    public void setItemClickListener(RecyclerViewOnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    //接口回调设置点击事件
    public interface RecyclerViewOnItemClickListener {
        //点击事件
        void onItemLongClick(View view, int position);
    }

02.使用自定义适配器:

01.给列表传递数据

    private void initData() {
        list = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            list.add("测试数据----" + i);
        }
        adapter = new RecyViewAdapter(MainActivity.this, list);
        RecyclerView.LayoutManager layoutManager = new GridLayoutManager(MainActivity.this, 3);
        recyView.setLayoutManager(layoutManager);
        recyView.setAdapter(adapter);
        adapter.notifyDataSetChanged();
        adapter.setItemClickListener(new RecyViewAdapter.RecyclerViewOnItemClickListener() {
            @Override
            public void onItemLongClick(View view, int position) {
                //多次点击删除
                if (prelongTim == 0) {
                    //第一次单击时间
                    prelongTim = System.currentTimeMillis();
                    Toast.makeText(MainActivity.this, "第一次点击的时间" + prelongTim, Toast.LENGTH_SHORT).show();
                } else {
                    curTime = System.currentTimeMillis();//本地单击的时间
                    if ((curTime - prelongTim) < 500) {
                        //连续点击删除数据
                        Toast.makeText(MainActivity.this, "点太快了,死鬼", Toast.LENGTH_SHORT).show();
                        adapter.delete(position);
                    }
                    prelongTim = 0; //重新开始统计点击时间差
                }
            }
        });
        recyView.setLongClickable(true);
        recyView.addItemDecoration(new GridSpacingItemDecoration(3, 80, true));
    }

02.设置页面上按钮的监听事件:

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.tv_over:
                if (!isSelect) {
                    isSelect = true;//全选
                    adapter.All();
                    tv_over.setText("取消全选");
                } else {
                    isSelect = false;//取消全选
                    adapter.neverall();
                    tv_over.setText("全选");
                }
                break;
            case R.id.commit:
                String content = "";
                listdatas.clear();
                Map<Integer, Boolean> map = adapter.getMap();
                for (int i = 0; i < list.size(); i++) {
                    if (map.get(i)) {
                        listdatas.add(list.get(i));
                    }
                }
                //将选中的数据显示出来,多个数据的话使用","分割
                for (int j = 0; j < listdatas.size(); j++) {
                    content += listdatas.get(j) + ",";
                }
                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                if (content.length() == 0) {
                    builder.setMessage("请选择数据");
                } else {
                    builder.setMessage(content.substring(0, content.length() - 1));
                }
                builder.create().show();

                break;
            case R.id.delete:
                Map<Integer, Boolean> tMap = adapter.getMap();
                for (int i = 0; i < list.size(); i++) {
                    if (tMap.get(i)) {
                        adapter.delete(i);
                    }
                }
                break;
            default:
                break;
        }
    }

五、自定义RecyclerView(item的拖拽监听)

01.创建自定义适配器:

01.创建自定义监听器

public interface ItemTouchListener {

    //数据交换
    void onItemMove(RecyclerView.ViewHolder source, RecyclerView.ViewHolder target);

    //数据删除
    void onItemDissmiss(RecyclerView.ViewHolder source);

    //drag或者swipe选中
    void onItemSelect(RecyclerView.ViewHolder source);

    //状态清除
    void onItemClear(RecyclerView.ViewHolder source);
}

02.创建自定义回调,处理RecycleView的选中,拖拽移动,拖拽删除的实现类

/**
 *
 * Description: 处理RecycleView的选中,拖拽移动,拖拽删除的实现类
 */
public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {

    private final ItemTouchListener touchListener;

    /**
     *
     * @param listener
     */
    public SimpleItemTouchHelperCallback(ItemTouchListener listener) {
        touchListener = listener;
    }

    @Override
    public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
        //int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; //允许上下的拖动
        //int dragFlags =ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT; //允许左右的拖动
        //int swipeFlags = ItemTouchHelper.LEFT; //只允许从右向左侧滑
        //int swipeFlags = ItemTouchHelper.DOWN; //只允许从上向下侧滑
        //一般使用makeMovementFlags(int,int)或makeFlag(int, int)来构造我们的返回值
        //makeMovementFlags(dragFlags, swipeFlags)

        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT; //允许上下左右的拖动
        return makeMovementFlags(dragFlags, 0);
    }

    @Override
    public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
        //通过接口传递拖拽交换数据的起始位置和目标位置的ViewHolder
        touchListener.onItemMove(viewHolder, target);
        return true;
    }

    @Override
    public boolean isLongPressDragEnabled() {
        return true;//长按启用拖拽
    }

    @Override
    public boolean isItemViewSwipeEnabled() {
        return false; //不启用拖拽删除
    }

    @Override
    public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
        //移动删除回调,如果不用可以不用理
        // mAdapter.onItemDissmiss(viewHolder);
    }

    @Override
    public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
        super.onSelectedChanged(viewHolder, actionState);
        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
            //当滑动或者拖拽view的时候通过接口返回该ViewHolder
            touchListener.onItemSelect(viewHolder);
        }
    }

    @Override
    public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);
        if (!recyclerView.isComputingLayout()) {
            //当需要清除之前在onSelectedChanged或者onChildDraw,onChildDrawOver设置的状态或者动画时通过接口返回该ViewHolder
            touchListener.onItemClear(viewHolder);
        }
    }
}

03.自定义适配器继承自定义监听器

/**
 * 
 * Description: 拖拽的recyclerView 的  adapter
 */
public class DragRecyclerViewAdapter extends RecyclerView.Adapter<DragRecyclerViewAdapter.DragHolder> implements ItemTouchListener {

    private Context context;
    private List<String> contentList = new ArrayList<>();
    private boolean isShowDelete = false;//是否显示删除图标

    public DragRecyclerViewAdapter(Context context, List<String> contentList) {
        this.context = context;
        this.contentList = contentList;
    }

    @NonNull
    @Override
    public DragHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.item_drag_recyclerview, parent, false);
        return new DragHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull final DragHolder holder, int position) {
        if (isShowDelete){
            holder.img_delete.setVisibility(View.VISIBLE);
            if (position == 0){
                holder.img_delete.setVisibility(View.GONE);
            }
        }else {
            holder.img_delete.setVisibility(View.GONE);
        }
        holder.tv_content.setText(contentList.get(position));

        //删除
        holder.img_delete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onItemDissmiss(holder);
            }
        });
    }

    @Override
    public int getItemCount() {
        return contentList.size();
    }

    @Override
    public void onItemMove(RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {
        int fromPosition = source.getAdapterPosition();
        int toPosition = target.getAdapterPosition();
        if (fromPosition == 0 || toPosition == 0){//这个判断根据实际修改,可加可不加
            Toast.makeText(context,"第一个不可移动",Toast.LENGTH_SHORT).show();
        }else {
            if (fromPosition < contentList.size() && toPosition < contentList.size()) {
                //交换数据位置,交换逻辑发生的地方
                Collections.swap(contentList, fromPosition, toPosition);
                //刷新位置交换
                notifyItemMoved(fromPosition, toPosition);
            }
            //移动过程中移除view的放大效果
            onItemClear(source);
        }
    }

    @Override
    public void onItemDissmiss(RecyclerView.ViewHolder source) {
        int position = source.getAdapterPosition();
        contentList.remove(position); //移除数据
        notifyItemRemoved(position);//刷新数据移除
    }

    @Override
    public void onItemSelect(RecyclerView.ViewHolder source) {
        //当拖拽选中时放大选中的view
        source.itemView.setScaleX(1.2f);
        source.itemView.setScaleY(1.2f);
    }

    @Override
    public void onItemClear(RecyclerView.ViewHolder source) {
        //拖拽结束后恢复view的状态
        source.itemView.setScaleX(1.0f);
        source.itemView.setScaleY(1.0f);
    }

    public class DragHolder extends RecyclerView.ViewHolder {
        private final TextView tv_content;
        private final ImageView img_delete;//删除图标

        public DragHolder(View itemView) {
            super(itemView);
            tv_content = itemView.findViewById(R.id.tv_content);
            img_delete = itemView.findViewById(R.id.img_delete);
        }
    }

    public void setContentList(Context context, List<String> contentList,boolean isShowDelete){
        this.context = context;
        this.contentList = contentList;
        this.isShowDelete = isShowDelete;
        notifyDataSetChanged();
    }
}

02.使用自定义适配器:

注意:使用SimpleItemTouchHelperCallback(dragRecyclerViewAdapter)传入一个dragRecyclerViewAdapter(ItemTouchListener的子类)

public class MainActivity extends AppCompatActivity {

    private ArrayList<String> contentList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setDragData();
        RecyclerView dragRecyclerView = findViewById(R.id.dragRecyclerView);
        //创建adapter
        DragRecyclerViewAdapter dragRecyclerViewAdapter = new DragRecyclerViewAdapter(this, contentList);
        //设置默认的布局方式
        dragRecyclerView.setLayoutManager(new GridLayoutManager(this, 5,GridLayoutManager.VERTICAL, false));
        //设置adapter
        dragRecyclerView.setAdapter(dragRecyclerViewAdapter);
        //创建SimpleItemTouchHelperCallback
        ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(dragRecyclerViewAdapter);
        //用Callback构造ItemtouchHelper
        ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
        //调用ItemTouchHelper的attachToRecyclerView方法建立联系
        touchHelper.attachToRecyclerView(dragRecyclerView);

        //编辑
        TextView tv_edit = findViewById(R.id.tv_edit);
        tv_edit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (tv_edit.getText().toString().equals("编辑")){
                    tv_edit.setText("完成");
                    dragRecyclerViewAdapter.setContentList(MainActivity.this, contentList,true);
                }else {
                    tv_edit.setText("编辑");
                    dragRecyclerViewAdapter.setContentList(MainActivity.this, contentList,false);
                }
            }
        });
    }

    private void setDragData(){
        for (int i = 1; i < 21; i++){
            contentList.add(i + "");
        }
    }
}

六、自定义RecyclerView下拉刷新

01.创建自定义适配器

01.继承RecyclerView.Adapter<RecyclerView.ViewHolder>(),定义FooterViewHolder

class NewsAdapter(val list: List<Data>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    private val Type_Footer = 0
    private val Type_Item = 1

    private var load_more_status: Int? = null
    val PULLUP_LOAD_MORE: Int = 3
    val LOADING_MORE: Int = 4
    val LOADING_END: Int = 5

    inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        var tvContent: TextView = view.findViewById(R.id.tvContent)
        var tvPlay: TextView = view.findViewById(R.id.tvPlay)
    }

    inner class FooterViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        var content: TextView = view.findViewById(R.id.content)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
       if(viewType == Type_Footer) {
           val view = LayoutInflater.from(parent.context).inflate(R.layout.layout_footer_item, parent, false)
           return FooterViewHolder(view)
       }
        val itemView = LayoutInflater.from(parent.context).inflate(R.layout.layout_joke_list_item, parent, false)
        return ViewHolder(itemView)
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (holder is NewsAdapter.FooterViewHolder) {
            when(load_more_status) {
                PULLUP_LOAD_MORE -> {
                    holder.content.text = "上拉加载更多"
                }
                LOADING_MORE -> {
                    holder.content.text = "正在加载更多数据..."
                }
                LOADING_END -> {
                    holder.content.text = "没有更多数据了..."
                }
            }
        } else if(holder is NewsAdapter.ViewHolder) {
            holder.tvContent.text = list[position].content
            holder.tvPlay.text = "播放"
        }
    }

    override fun getItemCount(): Int = list.size + 1

    override fun getItemViewType(position: Int): Int = if(position + 1 == itemCount) Type_Footer else Type_Item

    fun changMoreStatus(status: Int) {
        load_more_status = status
        notifyDataSetChanged()
    }

}

02.使用自定义适配器

class MainActivity : AppCompatActivity(), MainContract.View {

    //页码
    private var currentPage = 1

    //数据源
    private var mList = ArrayList<Data>()
    private lateinit var mJokeAdapter: NewsAdapter
    private lateinit var mJokeListView: RecyclerView
    private lateinit var mPresenter: MainPresenter
    private var totalItemCount: Int? = null
    private var lastVisibleItemPosition: Int? = null
    private var mLayoutManager: LinearLayoutManager? = null
    private lateinit var context: Context

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mJokeListView = findViewById(R.id.mJokeListView)
        context = applicationContext
        mPresenter = MainPresenter()
        initList()
        mPresenter.getData(0, 0)
        mJokeListView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                super.onScrollStateChanged(recyclerView, newState)
                totalItemCount = mLayoutManager?.itemCount
                //已经刷新到底部了
                if (newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItemPosition == (totalItemCount!! - 1)) {
                    currentPage += 1
                    mJokeAdapter.changMoreStatus(mJokeAdapter.LOADING_MORE)
                    Toast.makeText(context, "正在加载更多数据", Toast.LENGTH_SHORT).show()
                    mPresenter.getData(currentPage, 1)
                }
            }

            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)
                lastVisibleItemPosition = mLayoutManager?.findLastVisibleItemPosition()
            }
        })
    }

    override fun onStart() {
        super.onStart()
        mPresenter.takeView(this)
    }


    override fun refreshView(orientation: Int, data: List<Data>) {
        if (orientation == 0) {
            //列表要清空
            mList.clear()
            mList.addAll(data)
            Toast.makeText(this, "上拉加载更多数据", Toast.LENGTH_SHORT).show()
            mJokeAdapter.changMoreStatus(mJokeAdapter.PULLUP_LOAD_MORE)
            //适配器要全部刷新
            mJokeAdapter.notifyDataSetChanged()
        } else if (orientation == 1) {
            //上一次的最大值
            val positionStart = mList.size
            mList.addAll(data)
            Log.d("hy55", "$data")
            Toast.makeText(this, "上拉加载更多数据", Toast.LENGTH_SHORT).show()
            mJokeAdapter.changMoreStatus(mJokeAdapter.PULLUP_LOAD_MORE)
            //局部刷新
            mJokeAdapter.notifyItemRangeInserted(positionStart, data.size)
        }
    }

    override fun loadFinish() {
        Toast.makeText(this, "没有数据了", Toast.LENGTH_SHORT).show()
        mJokeAdapter.changMoreStatus(mJokeAdapter.LOADING_END)
    }


    //初始化列表
    private fun initList() {

        mLayoutManager = LinearLayoutManager(this)
        mJokeListView.layoutManager = mLayoutManager

        mJokeListView.layoutManager = LinearLayoutManager(this)
        mJokeAdapter = NewsAdapter(mList)
        //设置适配器
        mJokeListView.adapter = mJokeAdapter

    }
    
}

03.定义数据源

class MainPresenter : MainContract.Presenter {

    private var view: MainContract.View? = null

    override fun takeView(view: MainContract.View) {
        this.view = view
    }

    override fun dropView(view: MainContract.View) {
        this.view = null
    }

    fun getData(currentPage: Int, orientation: Int) {
        HttpManager.queryJokeList(currentPage, object : Callback<JokeListData> {
            override fun onFailure(call: Call<JokeListData>, t: Throwable) {
                L.i("onFailure$t")
            }

            override fun onResponse(call: Call<JokeListData>, response: Response<JokeListData>) {
                L.i("${response.body()?.error_code}")
                val data = listOf(
                    Data("123", "456", 1, "789"),
                    Data("123", "456", 1, "789"),
                    Data("123", "456", 1, "789")
                )
                //接口无法加载数据,测试数据是否能够正常加载
                if (orientation == 0) {
                    view?.refreshView(orientation, data)
                } else {
                    if (response.body()?.error_code == 10012) {
                        view?.loadFinish()
                    } else {
                        //追加在尾部
                        response.body()?.result?.data?.let {
                            view?.refreshView(orientation, it)
                        }
                    }
                }

            }
        })
    }
}

04.效果图

01.初始加载数据:
在这里插入图片描述
02.上拉刷新加载数据:
在这里插入图片描述


七、ListAdapter的使用

01.创建自定义适配器

01.自定义适配器继承ListAdapter

//构建ListView的Adapter,不需要将数据带进去
class NewsAdapter :
    ListAdapter<Data, NewsAdapter.ViewHolder>(Diff()) {

    //构建ListView的数据比较结果
    class Diff : DiffUtil.ItemCallback<Data>() {
        override fun areItemsTheSame(oldItem: Data, newItem: Data): Boolean {
            return oldItem == newItem
        }

        override fun areContentsTheSame(oldItem: Data, newItem: Data): Boolean {
            return oldItem.hashId == newItem.hashId
        }
    }

    inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val tvContent: TextView = view.findViewById(R.id.tvContent)
        var tvPlay: TextView = view.findViewById(R.id.tvPlay)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.layout_joke_list_item, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.tvContent.text = getItem(position).content
        holder.tvPlay.text = "播放"
    }
}

02.使用自定义适配器

class MainActivity : AppCompatActivity(), OnRefreshListener, OnRefreshLoadMoreListener,
    MainContract.View {

    //页码
    private var currentPage = 1

    //数据源
    private var mList = ArrayList<Data>()

    private lateinit var mJokeAdapter: NewsAdapter

    private var refreshLayout: SmartRefreshLayout? = null
    private var mJokeListView: RecyclerView? = null
    private lateinit var mPresenter: MainPresenter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        refreshLayout = findViewById(R.id.refreshLayout)
        mJokeListView = findViewById(R.id.mJokeListView)
        mJokeAdapter = NewsAdapter()
        mPresenter = MainPresenter()
        initList()
        mPresenter.getData(0, 0)
    }

    override fun onStart() {
        super.onStart()
        mPresenter.takeView(this)
    }
    
    override fun refreshView(orientation: Int, data: List<Data>) {
        if (orientation == 0) {
            refreshLayout?.finishRefresh()
            //列表要清空
            mList.clear()
            mList.addAll(data)
            mJokeAdapter.submitList(mList)
            //适配器要全部刷新
            mJokeAdapter.notifyDataSetChanged()
        } else if (orientation == 1) {
            refreshLayout?.finishLoadMore()

            //上一次的最大值
            val positionStart = mList.size

            mList.addAll(data)
            //需要注意:submitList()方法不能传参数MutableList
            mJokeAdapter.submitList(mList)
            //局部刷新
            mJokeAdapter.notifyItemRangeInserted(positionStart, data.size)

        }
    }

    //初始化列表
    private fun initList() {

        //刷新组件
        refreshLayout?.setRefreshHeader(ClassicsHeader(this))
        refreshLayout?.setRefreshFooter(ClassicsFooter(this))

        //监听
        refreshLayout?.setOnRefreshListener(this)
        refreshLayout?.setOnRefreshLoadMoreListener(this)

        mJokeListView?.layoutManager = LinearLayoutManager(this)

        //设置适配器
        mJokeListView?.adapter = mJokeAdapter

    }

    override fun onRefresh(refreshLayout: RefreshLayout) {
        currentPage = 1
        mPresenter.getData(currentPage, 0)
    }

    override fun onLoadMore(refreshLayout: RefreshLayout) {
        currentPage += 1
        mPresenter.getData(currentPage, 1)
    }
}

八、自定义提示框

01.布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:id="@+id/layout1"
    android:gravity="center_horizontal"
    >
    <Button android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="显示带取消、中立和确定按钮的对话框"/>

    <Button android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="显示列表的对话框"/>

    <Button android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="显示带单选列表对话框"/>

    <Button android:id="@+id/button4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="显示带多选列表对话框"/>

</LinearLayout>

02.创建并使用自定义提示框(四种样式的提示框)

public class MainActivity extends AppCompatActivity {
    private boolean[] checkedItems;//记录各个列表项的状态
    private String[] items;//各列表项要显示的内容

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //显示带取消、中立和确定按钮的对话框
        Button button1 = (Button) findViewById(R.id.button1);
        button1.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View arg0) {
                AlertDialog alert = new AlertDialog.Builder(MainActivity.this).create();
                alert.setIcon(R.mipmap.ic_launcher);//设置对话框的图标
                alert.setTitle("系统提示");//设置对话框的标题
                alert.setMessage("显示带取消、中立和确定按钮的对话框!");//设置对话框显示的内容
                //添加“取消”按钮
                alert.setButton(DialogInterface.BUTTON_NEGATIVE, "取消",
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                Toast.makeText(MainActivity.this, "您单击了取消按钮", Toast.LENGTH_SHORT).show();
                            }
                        });
                //添加“确定”按钮
                alert.setButton(DialogInterface.BUTTON_POSITIVE, "确定",
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                Toast.makeText(MainActivity.this, "您单击了确定按钮", Toast.LENGTH_SHORT).show();
                            }
                        });
                //添加“中立”按钮
                alert.setButton(DialogInterface.BUTTON_NEUTRAL, "中立",
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                Toast.makeText(MainActivity.this, "您单击了中立按钮", Toast.LENGTH_SHORT).show();
                            }
                        });
                alert.show();//显示对话框
            }
        });


        //显示列表的对话框
        Button button2 = findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View arg0) {
                final String[] items = new String[]{"唱歌", "跳舞", "美术", "远足旅行", "摄影"};
                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                builder.setIcon(R.mipmap.ic_launcher);
                builder.setTitle("请选择你的爱好:");
                //添加列表项
                builder.setItems(items, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Toast.makeText(MainActivity.this, "您选择了" + items[which], Toast.LENGTH_SHORT).show();
                    }
                });
                builder.create().show();//创建对话框并显示
            }
        });

        //显示带单选列表对话框
        Button button3 = (Button) findViewById(R.id.button3);
        button3.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View arg0) {
                final String[] items = new String[]{"标准", "无声", "会议", "户外", "离线"};
                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                builder.setIcon(R.mipmap.ic_launcher);
                builder.setTitle("请选择要使用的情景模式:");
                builder.setSingleChoiceItems(items, 0, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Toast.makeText(MainActivity.this, "您选择了" + items[which], Toast.LENGTH_SHORT).show();
                    }

                });
                builder.setPositiveButton("确定", null);
                builder.create().show();//创建对话框并显示
            }
        });

        //显示带多选列表对话框
        Button button4 = (Button) findViewById(R.id.button4);
        button4.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                checkedItems = new boolean[]{false, true, false, true, false};//记录各列表的状态
                //各列表项要显示的内容
                items = new String[]{"植物大战僵尸", "愤怒的小鸟", "泡泡龙", "开心消消乐", "地铁跑酷"};
                //显示带单选列表框的对话框
                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                builder.setIcon(R.mipmap.ic_launcher);
                builder.setTitle("请选择您喜欢的游戏:");
                builder.setMultiChoiceItems(items, checkedItems, new DialogInterface.OnMultiChoiceClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which, boolean isChecked) {
                        checkedItems[which] = isChecked;
                    }
                });
                //为对话框添加"确定"按钮
                builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        String result = "";
                        for (int i = 0; i < checkedItems.length; i++) {
                            if (checkedItems[i]) {
                                result += items[i] + "、";
                            }
                        }
                        //当result不为空时,通过消息提示框显示选择的结果
                        if (!"".equals(result)) {
                            result = result.substring(0, result.length() - 1);//去掉最后的"、"号
                            Toast.makeText(MainActivity.this, "您选择了:[" + result + "]", Toast.LENGTH_SHORT).show();
                        }
                    }
                });
                builder.create().show();//创建对话框并显示
            }
        });
    }
}

九、自定义列表对话框

01.创建列表的适配器,设置监听事件(监听事件在适配器中处理)

public class DialogListAdapter extends BaseAdapter {

    private List<HashMap<String,String>> mapList;
    private LayoutInflater inflater;
    private Context mContext;

    public DialogListAdapter(Context context, List<HashMap<String,String>>list){
        this.mContext = context;
        this.mapList = list;
        inflater = LayoutInflater.from(mContext);

    }

    @Override
    public int getCount() {
        return mapList.size();  //数据源的长度
    }

    @Override
    public Object getItem(int position) {
        return mapList.get(position); //返回数据源的其中某一个对象
    }

    @Override
    public long getItemId(int position) {
        return position; //返回adapter中的其中一个项的id
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        myViewHolder hv;
        if (null==convertView){
            hv = new myViewHolder();
            convertView = inflater.inflate(R.layout.adapter_dialog_view,null,false);
            hv.tvName = convertView.findViewById(R.id.tvName);
            hv.tvCountry = convertView.findViewById(R.id.tvCountry);
            hv.agree = convertView.findViewById(R.id.agree);
            hv.disAgree = convertView.findViewById(R.id.disAgree);
            convertView.setTag(hv);

        }else {
            hv = (myViewHolder)convertView.getTag();
        }
        //一定要判刑断下数据源是否为空,否则很大几率就crash了
        if (mapList!=null && !mapList.isEmpty()){
            hv.tvName.setText(mapList.get(position).get("name"));
            hv.tvCountry.setText(mapList.get(position).get("country"));
            //设置监听事件
            hv.agree.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(mContext, mapList.get(position).get("name"), Toast.LENGTH_SHORT).show();
                }
            });
            //设置监听事件
            hv.disAgree.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(mContext, mapList.get(position).get("country"), Toast.LENGTH_SHORT).show();
                }
            });
        }
        return convertView;
    }

    class myViewHolder{
        TextView tvName;
        TextView tvCountry;
        Button agree;
        Button disAgree;
    }

}

02.创建列表对话框

public class MainActivity extends AppCompatActivity {

    private DialogListAdapter listAdapter;
    private List<HashMap<String, String>> lists = new ArrayList<>();
    String[] names = {"Tom", "jane", "kangkang", "mike"};
    String[] countries = {"china", "japan", "germany", "usa"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initViewDatas();
        findViewById(R.id.bt).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                show_adapter(v);
            }
        });

    }
    /**
     * 初始化adapter并填充数据
     */
    void initViewDatas() {
        listAdapter = new DialogListAdapter(this, lists);
        for (int i = 0; i < 10; i++) {
            HashMap<String, String> map = new HashMap<>();
            map.put("name", names[i % 4]);
            map.put("country", countries[i % 4]);
            lists.add(map);
        }
        listAdapter.notifyDataSetChanged();

    }

    //这是一个button的onclick事件
    public void show_adapter(View view) {

        AlertDialog.Builder adapterBuilder = new AlertDialog.Builder(this);
        adapterBuilder.setIcon(R.mipmap.ic_launcher_round);
        adapterBuilder.setTitle("This is Adapter Dialog");
        ListView listView = new ListView(this);

        adapterBuilder.setView(listView);
        listView.setAdapter(listAdapter);

        //设置适配器,设置监听事件,但是点击后会退出对话框
//        adapterBuilder.setAdapter(listAdapter, new DialogInterface.OnClickListener() {
//            @Override
//            public void onClick(DialogInterface dialog, int which) {
//                HashMap<String, String> selectMap = (HashMap<String, String>) listAdapter.getItem(which);
//                Toast.makeText(MainActivity.this, selectMap.get("name"), Toast.LENGTH_SHORT).show();
//            }
//        });

        adapterBuilder.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                //do what you want
            }
        });

        adapterBuilder.setNegativeButton("Cancle", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                //do what you want
            }
        });
        adapterBuilder.show();
    }
}

总结

终于完成这份总结,虽然还没有囊括所有的Android自定义控件,但这只是开始,后续我还会持续更新更多有关Android自定义控件的内容,欢迎大家点赞、关注、收藏!!!
在这里插入图片描述

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值