安卓实现饿了么点餐界面效果(京东类别左右列表联动)

没有效果图的示例简直就是扯淡

在这里插入图片描述


通过两个RecyclerView实现左右联动,吸附的标题通过自定义分割线view实现


源码在最下面👇

废话不多说,直接看代码吧。

Mainactivity.class

package com.cc.test;

import android.graphics.Color;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import com.cc.test.adapter.LeftAdapter;
import com.cc.test.adapter.RightAdapter;
import com.cc.test.listener.RightItemCheckListener;
import com.cc.test.model.RightBean;
import com.cc.test.model.SortBean;
import com.cc.test.view.ItemHeaderDecoration;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * 主界面
 * 14点57分
 *
 * @author cc
 */
public class MainActivity extends AppCompatActivity implements RightItemCheckListener {

    //左边的rv列表和适配器
    private RecyclerView mRvLeft;
    private LeftAdapter leftAdapter;
    private LinearLayoutManager mLinearLayoutManager;

    //右边的rv列表和适配器
    private RecyclerView mRvRight;
    private RightAdapter rightAdapter;
    private GridLayoutManager mManager;

    //总数据源
    private List<SortBean> mSortBean = new ArrayList<>();
    //右边的数据源
    private List<RightBean> rightBeanList = new ArrayList<>();

    //头部吸附自定义分割线
    private ItemHeaderDecoration mDecoration;
    private boolean move = false;
    //标量
    private int mIndex = 0;

    //点击左边某一个具体的item的位置
    private int targetPosition;
    //是否可移动
    private boolean isMoved;

    /**
     * 开始
     *
     * @param savedInstanceState
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //加载默认数据
        getDefaultData();
        //初始化控件
        initLeftView();
        initRightView();
    }

    /**
     * 获取默认数据
     */
    private void getDefaultData() {
        //获取asset目录下的资源文件
        String assetsData = getAssetsData("data.json");
        Gson gson = new Gson();
        //mSortBean就是要使用的集合啦
        mSortBean = gson.fromJson(assetsData, new TypeToken<List<SortBean>>() {
        }.getType());
    }

    /**
     * 初始化左边的rv及绑定适配器
     */
    private void initLeftView() {
        mRvLeft = (RecyclerView) findViewById(R.id.rv_left);
        mLinearLayoutManager = new LinearLayoutManager(this);
        mRvLeft.setLayoutManager(mLinearLayoutManager);
        DividerItemDecoration decoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL);
        mRvLeft.addItemDecoration(decoration);

        //左边的item要填充的内容啦
        List<String> leftTitle = new ArrayList<>();
        //初始化左侧列表数据
        for (int i = 0; i < mSortBean.size(); i++) {
            leftTitle.add(mSortBean.get(i).getName());
        }
        //左边的adapter开始实现并处理item的点击事件
        leftAdapter = new LeftAdapter(this, leftTitle, (id, position) -> {
            //可以移动啦
            isMoved = true;
            //目标的下标
            targetPosition = position;
            //去设置
            setChecked(position, true);
        });
        //绑定适配器
        mRvLeft.setAdapter(leftAdapter);
    }

    /**
     * 创建右边的布局
     */
    private void initRightView() {
        //获取右边的控件
        mRvRight = (RecyclerView) findViewById(R.id.rv_right);
        //监听右边的滑动事件
        mRvRight.addOnScrollListener(new RecyclerViewListener());
        //创建item布局管理器
        mManager = new GridLayoutManager(this, 3);
        //通过isTitle的标志来判断是否是头部吸附title
        mManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                return rightBeanList.get(position).isTitle() ? 3 : 1;
            }
        });
        //设置布局管理
        mRvRight.setLayoutManager(mManager);
        //点击事件
        rightAdapter = new RightAdapter(this, rightBeanList, (id, position) -> {
            String content = "";
            switch (id) {
                case R.id.root:
                    content = "title";
                    break;
                case R.id.content:
                    content = "content";
                    break;

            }
            Snackbar snackbar = Snackbar.make(mRvRight, "当前点击的是" + content + ":" + rightBeanList.get(position).getName(), Snackbar.LENGTH_SHORT);
            View mView = snackbar.getView();
            mView.setBackgroundColor(Color.BLUE);
            TextView text = mView.findViewById(android.support.design.R.id.snackbar_text);
            text.setTextColor(Color.WHITE);
            text.setTextSize(25);
            snackbar.show();
        });
        //绑定适配器
        mRvRight.setAdapter(rightAdapter);
        //创建一个自定义分割线,给右边的列表布局添加上
        mDecoration = new ItemHeaderDecoration(this, rightBeanList);
        mRvRight.addItemDecoration(mDecoration);
        //分割线点击事件
        mDecoration.setCheckListener(this);
        //循环遍历数据源
        for (int i = 0; i < mSortBean.size(); i++) {
            //将一级数据添加为头部数据(就左边的列表数据)
            RightBean head = new RightBean(mSortBean.get(i).getName());
            //头部设置为true
            head.setTitle(true);
            head.setTitleName(mSortBean.get(i).getName());
            //这里的tag不能为空,不能重复,是滑动对比用的
            head.setTag(String.valueOf(i));
            //添加到右边的数据源集合里面去
            rightBeanList.add(head);
            //开始遍历二级数据,真正在右边item加载的数据
            List<SortBean.CategoryTwoArrayBean> categoryTwoArray = mSortBean.get(i).getCategoryTwoArray();
            //遍历
            for (int j = 0; j < categoryTwoArray.size(); j++) {
                //遍历每一个并添加到右边的数据源里面
                RightBean body = new RightBean(categoryTwoArray.get(j).getName());
                body.setTag(String.valueOf(i));
                String name = mSortBean.get(i).getName();
                body.setTitleName(name);
                rightBeanList.add(body);
            }
        }
        //刷新
        rightAdapter.notifyDataSetChanged();
        mDecoration.setData(rightBeanList);
    }

    /**
     * 右边列表的滑动事件
     */
    private class RecyclerViewListener extends RecyclerView.OnScrollListener {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            if (move && newState == RecyclerView.SCROLL_STATE_IDLE) {
                move = false;
                int n = mIndex - mManager.findFirstVisibleItemPosition();
                if (0 <= n && n < mRvRight.getChildCount()) {
                    int top = mRvRight.getChildAt(n).getTop();
                    mRvRight.smoothScrollBy(0, top);
                }
            }
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            if (move) {
                move = false;
                int n = mIndex - mManager.findFirstVisibleItemPosition();
                if (0 <= n && n < mRvRight.getChildCount()) {
                    int top = mRvRight.getChildAt(n).getTop();
                    mRvRight.scrollBy(0, top);
                }
            }
        }
    }

    /**
     * 选中的点击事件
     *
     * @param position 当前下标
     * @param isLeft   是否是左边的
     */
    private void setChecked(int position, boolean isLeft) {
        //左边
        if (isLeft) {
            leftAdapter.setCheckedPosition(position);
            //此处的位置需要根据每个分类的集合来进行计算
            int count = 0;
            for (int i = 0; i < position; i++) {
                count += mSortBean.get(i).getCategoryTwoArray().size();
            }
            count += position;
            mIndex = count;
            mRvRight.stopScroll();
            smoothMoveToPosition(count);
            ItemHeaderDecoration.setCurrentTag(String.valueOf(targetPosition));//凡是点击左边,将左边点击的位置作为当前的tag
        } else {
            //右边
            if (isMoved) {
                isMoved = false;
            } else {
                leftAdapter.setCheckedPosition(position);
            }
            ItemHeaderDecoration.setCurrentTag(String.valueOf(position));//如果是滑动右边联动左边,则按照右边传过来的位置作为tag
        }
        //移动到中间
        moveToCenter(position);
    }

    /**
     * 滑动到某一个位置
     *
     * @param n
     */
    private void smoothMoveToPosition(int n) {
        int firstItem = mManager.findFirstVisibleItemPosition();
        int lastItem = mManager.findLastVisibleItemPosition();
        if (n <= firstItem) {
            mRvRight.scrollToPosition(n);
        } else if (n <= lastItem) {
            int top = mRvRight.getChildAt(n - firstItem).getTop();
            mRvRight.scrollBy(0, top);
        } else {
            mRvRight.scrollToPosition(n);
            move = true;
        }
    }

    /**
     * 将当前左边选中的item居中
     *
     * @param position
     */
    private void moveToCenter(int position) {
        //将点击的position转换为当前屏幕上可见的item的位置以便于计算距离顶部的高度,从而进行移动居中
        View childAt = mRvLeft.getChildAt(position - mLinearLayoutManager.findFirstVisibleItemPosition());
        if (childAt != null) {
            int y = (childAt.getTop() - mRvLeft.getHeight() / 2);
            mRvLeft.smoothScrollBy(0, y);
        }
    }

    /**
     * 右边item事件
     *
     * @param position
     * @param isScroll
     */
    @Override
    public void check(int position, boolean isScroll) {
        setChecked(position, isScroll);
    }

    /**
     * 从资源文件中获取分类json
     *
     * @param path
     * @return
     */
    private String getAssetsData(String path) {
        String result = "";
        try {
            //获取输入流
            InputStream mAssets = getAssets().open(path);
            //获取文件的字节数
            int length = mAssets.available();
            //创建byte数组
            byte[] buffer = new byte[length];
            //将文件中的数据写入到字节数组中
            mAssets.read(buffer);
            mAssets.close();
            result = new String(buffer);
            return result;
        } catch (IOException e) {
            e.printStackTrace();
            Log.e("fuck", e.getMessage());
            return result;
        }
    }

}

LeftAdapter.class

package com.cc.test.adapter;

import android.content.Context;
import android.graphics.Color;
import android.view.View;
import android.widget.TextView;

import com.cc.test.R;
import com.cc.test.listener.RvItemClickListener;
import com.cc.test.base.BaseAdapter;
import com.cc.test.base.BaseHolder;

import java.util.List;

/**
 * 左边列表的适配器
 * 14点57分
 *
 * @author cc
 */
public class LeftAdapter extends BaseAdapter<String> {

    //当前选中的item下标
    private int checkedPosition;

    /**
     * 获取当前选中的item下标
     *
     * @param checkedPosition
     */
    public void setCheckedPosition(int checkedPosition) {
        this.checkedPosition = checkedPosition;
        notifyDataSetChanged();
    }

    /**
     * 构造方法
     *
     * @param context  上下文
     * @param list     item数据集合
     * @param listener 点击事件
     */
    public LeftAdapter(Context context, List<String> list, RvItemClickListener listener) {
        super(context, list, listener);
    }

    /**
     * 获取布局文件
     *
     * @param viewType
     * @return
     */
    @Override
    protected int getLayoutId(int viewType) {
        return R.layout.item_left_detail;
    }

    /**
     * holder
     *
     * @param view
     * @param viewType
     * @return
     */
    @Override
    protected BaseHolder getHolder(View view, int viewType) {
        return new SortHolder(view, listener);
    }

    /**
     * 处理数据交互
     */
    private class SortHolder extends BaseHolder<String> {

        //左边的item内容
        private TextView tvName;

        SortHolder(View itemView, RvItemClickListener listener) {
            super(itemView, listener);
            tvName = itemView.findViewById(R.id.tv_sort);
        }

        @Override
        public void bindHolder(String string, int position) {
            //填充左边的item内容
            tvName.setText(string);
            //处理选中与未选中的ui状态
            tvName.setBackgroundColor(
                    position == checkedPosition
                            ? Color.parseColor("#f3f3f3")
                            : Color.parseColor("#FFFFFF")
            );
            tvName.setTextColor(
                    position == checkedPosition
                            ? Color.parseColor("#0068cf")
                            : Color.parseColor("#1e1d1d")
            );
        }

    }
}

RightAdapter.class

package com.cc.test.adapter;

import android.content.Context;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import com.cc.test.R;
import com.cc.test.listener.RvItemClickListener;
import com.cc.test.base.BaseAdapter;
import com.cc.test.base.BaseHolder;
import com.cc.test.model.RightBean;

import java.util.List;

/**
 * 右边的适配器
 * 14点58分
 *
 * @author cc
 */
public class RightAdapter extends BaseAdapter<RightBean> {

    /**
     * 构造方法
     *
     * @param context
     * @param list
     * @param listener
     */
    public RightAdapter(Context context, List<RightBean> list, RvItemClickListener listener) {
        super(context, list, listener);
    }

    /**
     * 布局view
     *
     * @param viewType
     * @return
     */
    @Override
    protected int getLayoutId(int viewType) {
        return viewType == 0 ? R.layout.item_right_title : R.layout.item_right_detail;
    }

    /**
     * 根据isTitle确认是否未头部吸附布局
     *
     * @param position
     * @return
     */
    @Override
    public int getItemViewType(int position) {
        return list.get(position).isTitle() ? 0 : 1;
    }

    @Override
    protected BaseHolder getHolder(View view, int viewType) {
        return new ClassifyHolder(view, viewType, listener);
    }

    public class ClassifyHolder extends BaseHolder<RightBean> {
        TextView tvCity;
        ImageView avatar;
        TextView tvTitle;

        public ClassifyHolder(View itemView, int type, RvItemClickListener listener) {
            super(itemView, listener);
            switch (type) {
                case 0:
                    tvTitle = itemView.findViewById(R.id.tv_title);
                    break;
                case 1:
                    tvCity = itemView.findViewById(R.id.tvCity);
                    avatar = itemView.findViewById(R.id.ivAvatar);
                    break;
            }

        }

        @Override
        public void bindHolder(RightBean sortBean, int position) {
            int itemViewType = RightAdapter.this.getItemViewType(position);
            switch (itemViewType) {
                case 0:
                    tvTitle.setText(sortBean.getName());
                    break;
                case 1:
                    tvCity.setText(sortBean.getName());
                    break;
            }

        }
    }
}

自定义分割线

package com.cc.test.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.cc.test.R;
import com.cc.test.listener.RightItemCheckListener;
import com.cc.test.model.RightBean;

import java.util.List;

/**
 * 吸附的分割布局
 * 14点57分
 *
 * @author cc
 */
public class ItemHeaderDecoration extends RecyclerView.ItemDecoration {

    private int mTitleHeight;
    private List<RightBean> mDatas;
    private LayoutInflater mInflater;
    private RightItemCheckListener mCheckListener;
    public static String currentTag = "0";//标记当前左侧选中的position,因为有可能选中的item,右侧不能置顶,所以强制替换掉当前的tag

    public void setCheckListener(RightItemCheckListener checkListener) {
        mCheckListener = checkListener;
    }

    public ItemHeaderDecoration(Context context, List<RightBean> datas) {
        this.mDatas = datas;
        Paint paint = new Paint();
        mTitleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, context.getResources().getDisplayMetrics());
        int titleFontSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, context.getResources().getDisplayMetrics());
        paint.setTextSize(titleFontSize);
        paint.setAntiAlias(true);
        mInflater = LayoutInflater.from(context);
    }

    public ItemHeaderDecoration setData(List<RightBean> mDatas) {
        this.mDatas = mDatas;
        return this;
    }

    public static void setCurrentTag(String currentTag) {
        ItemHeaderDecoration.currentTag = currentTag;
    }

    @Override
    public void onDrawOver(Canvas canvas, final RecyclerView parent, RecyclerView.State state) {
        GridLayoutManager manager = (GridLayoutManager) parent.getLayoutManager();
        GridLayoutManager.SpanSizeLookup spanSizeLookup = manager.getSpanSizeLookup();
        int pos = ((LinearLayoutManager) (parent.getLayoutManager())).findFirstVisibleItemPosition();
        int spanSize = spanSizeLookup.getSpanSize(pos);
        Log.d("pos--->", String.valueOf(pos));
        String tag = mDatas.get(pos).getTag();
        View child = parent.findViewHolderForLayoutPosition(pos).itemView;
        boolean isTranslate = false;//canvas是否平移的标志
        if (!TextUtils.equals(mDatas.get(pos).getTag(), mDatas.get(pos + 1).getTag())
                || !TextUtils.equals(mDatas.get(pos).getTag(), mDatas.get(pos + 2).getTag())
                || !TextUtils.equals(mDatas.get(pos).getTag(), mDatas.get(pos + 3).getTag())
        ) {
            tag = mDatas.get(pos).getTag();
            int i = child.getHeight() + child.getTop();
            Log.d("i---->", String.valueOf(i));
            if (spanSize == 1) {
                //body 才平移
                if (child.getHeight() + child.getTop() < mTitleHeight) {
                    canvas.save();
                    isTranslate = true;
                    int height = child.getHeight() + child.getTop() - mTitleHeight;
                    canvas.translate(0, height);
                }
            }
        }
        drawHeader(parent, pos, canvas);
        if (isTranslate) {
            canvas.restore();
        }
        Log.d("tag--->", tag + "VS" + currentTag);
        if (!TextUtils.equals(tag, currentTag)) {
            currentTag = tag;
            Integer integer = Integer.valueOf(tag);
            mCheckListener.check(integer, false);
        }
    }

    /**
     * @param parent
     * @param pos
     */
    private void drawHeader(RecyclerView parent, int pos, Canvas canvas) {
        View topTitleView = mInflater.inflate(R.layout.item_right_title, parent, false);
        TextView tvTitle = (TextView) topTitleView.findViewById(R.id.tv_title);
        tvTitle.setText(mDatas.get(pos).getTitleName());
        //绘制title开始
        int toDrawWidthSpec;//用于测量的widthMeasureSpec
        int toDrawHeightSpec;//用于测量的heightMeasureSpec
        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) topTitleView.getLayoutParams();
        if (lp == null) {
            lp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);//这里是根据复杂布局layout的width height,new一个Lp
            topTitleView.setLayoutParams(lp);
        }
        topTitleView.setLayoutParams(lp);
        if (lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
            //如果是MATCH_PARENT,则用父控件能分配的最大宽度和EXACTLY构建MeasureSpec
            toDrawWidthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight(), View.MeasureSpec.EXACTLY);
        } else if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
            //如果是WRAP_CONTENT,则用父控件能分配的最大宽度和AT_MOST构建MeasureSpec
            toDrawWidthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight(), View.MeasureSpec.AT_MOST);
        } else {
            //否则则是具体的宽度数值,则用这个宽度和EXACTLY构建MeasureSpec
            toDrawWidthSpec = View.MeasureSpec.makeMeasureSpec(lp.width, View.MeasureSpec.EXACTLY);
        }
        //高度同理
        if (lp.height == ViewGroup.LayoutParams.MATCH_PARENT) {
            toDrawHeightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight() - parent.getPaddingTop() - parent.getPaddingBottom(), View.MeasureSpec.EXACTLY);
        } else if (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
            toDrawHeightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight() - parent.getPaddingTop() - parent.getPaddingBottom(), View.MeasureSpec.AT_MOST);
        } else {
            toDrawHeightSpec = View.MeasureSpec.makeMeasureSpec(mTitleHeight, View.MeasureSpec.EXACTLY);
        }
        //依次调用 measure,layout,draw方法,将复杂头部显示在屏幕上
        topTitleView.measure(toDrawWidthSpec, toDrawHeightSpec);
        topTitleView.layout(parent.getPaddingLeft(), parent.getPaddingTop(), parent.getPaddingLeft() + topTitleView.getMeasuredWidth(), parent.getPaddingTop() + topTitleView.getMeasuredHeight());
        topTitleView.draw(canvas);//Canvas默认在视图顶部,无需平移,直接绘制
        //绘制title结束
    }

}

还有部分代码就不一一贴出来了。


呐,代码就这些啦,简单吧~~

附上demo源码。

源码:源码请点这里

如果下不了源码,请评论带邮箱。


phone:18588400509
email:mr.cai_cai@foxmail.com

如果有什么问题,欢迎大家指导。并相互联系,希望能够通过文章互相学习。

											                               	---财财亲笔
  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

谁抢我的小口口

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

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

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

打赏作者

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

抵扣说明:

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

余额充值