This article is slightly modified on the basis of the avatar of ZXT in the future. Before reading this blog, you can go out and turn right to here: portal —–> click on the link to open

Ok, let’s first take a look at what the interfaces of the two apps look like:



We see that the two interfaces are very similar, if you have read the blog content I recommend, it will be very simple, first of all, we still have a mindless custom viewgroup, this interface I plan to use two recyclerView, because it is left and right layout, We directly inherit the Linearlayout and landscape layout, so we’ll just write:

public class MeiTuanFoodView extends LinearLayout {
   

    public MeiTuanFoodView(Context context) {
        this(context, null);
    }

    public MeiTuanFoodView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MeiTuanFoodView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOrientation(HORIZONTAL);

    }
Copy the code


Next we write an XML layout, which is a recyclerView on the left and a recyclerView on the right:


    
    
Copy the code

Because the outer layout is also a Linearlayout, so we can write merge layout directly, and initialize it in our viewGroup. And since our weight property is not fixed in the project, we try to support dynamic weight changes through custom properties. So we create a custom attribute in attrs.xml: the first two are obviously our weight attribute around recyclerView, and the last is the height of our hover tag.

Then our viewGroup code will look like this:

public MeiTuanFoodView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.context = context; setOrientation(HORIZONTAL); inflate(context, R.layout.layout_meituan, this); leftView = (RecyclerView) findViewById(R.id.left_view); rightView = (RecyclerView) findViewById(R.id.right_view); leftView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); rightView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MeiTuanFoodView, defStyleAttr, 0); leftSum = ta.getInt(R.styleable.MeiTuanFoodView_leftSum, 1); rightSum = ta.getInt(R.styleable.MeiTuanFoodView_rightSum, 2); itemTopHeight = ta.getDimensionPixelSize(R.styleable.MeiTuanFoodView_topItemHeight, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, context.getResources().getDisplayMetrics())); ta.recycle(); leftLp = (LayoutParams) leftView.getLayoutParams(); rightLp = (LayoutParams) rightView.getLayoutParams(); if (leftSum ! = leftLp.weight) { leftLp.weight = leftSum; leftView.setLayoutParams(leftLp); } if (rightSum ! = rightLp.weight) { rightLp.weight = rightSum; rightView.setLayoutParams(rightLp); }}Copy the code

Well, next we set the dynamic data, first of all two recyclerView must need adapter adapter, for simplicity, I directly wrote a simple version of baseAdapter to share, the code is very simple, we can see the understand:

public abstract class BaseViewAdapter extends RecyclerView.Adapter { private int selectPosition=-1; private Context context; private List mData; public BaseViewAdapter(Context context,List mData){ this.context=context; this.mData=mData; } @Override public BaseHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new BaseHolder(LayoutInflater.from(context).inflate(getLayoutId(),parent,false)); } @Override public void onBindViewHolder(BaseHolder holder, int position) { bind(holder,position); } public int getSelectPosition() { return selectPosition; } public void setSelectPosition(int selectPosition) { this.selectPosition = selectPosition; notifyDataSetChanged(); } @Override public int getItemCount() { return mData.size(); } protected abstract void bind(BaseHolder holder, int position); public abstract int getLayoutId() ; public static class BaseHolder extends RecyclerView.ViewHolder { private SparseArray mViews = new SparseArray<>(); private View mConvertView; public BaseHolder(View itemView) { super(itemView); mConvertView = itemView; } public T findViews(int resId) { View view = mViews.get(resId); if(null==view){ view=mConvertView.findViewById(resId); mViews.put(resId,view); } return (T)view; }}Copy the code


We use abstract methods in abstract classes to hand over the implementation to subclasses so that we can write less code and be happy to be lazy.

Then we started to think about how to set up the data, of course we don’t need to think about that complicated when we write the project, but as a human being, we don’t pretend to be pushy, we don’t fight, we don’t write the framework, are we still good friends?





So the first thing we consider about interaction such as hungry? About two columns of data should be a common tag or id to match, and the one on the right also need the tag for suspension, considering the real project will typically have the tag, so here we just use the tag to determine, as a result, we choose to fit through a baseBean:

public abstract class BaseMeiTuanBean {
   public abstract String tagStr();
  // public abstract long tagInt();
}Copy the code

In the actual project we inherit the BaseBean from the entity classes in the left and right columns, and return the tag data we need. Here I write two testBeans:

public class RightBean extends BaseMeiTuanBean { private String tag; private String text; public String getTag() { return tag; } public void setTag(String tag) { this.tag = tag; } public String getText() { return text; } public void setText(String text) { this.text = text; } @Override public String tagStr() { return tag; } @Override public long tagInt() { return 0; }}Copy the code
public class LeftBean extends BaseMeiTuanBean { private String tag; private int id; public String getTag() { return tag; } public void setTag(String tag) { this.tag = tag; } public int getId() { return id; } public void setId(int id) { this.id = id; } @Override public String tagStr() { return tag; } @Override public long tagInt() { return 0; }}Copy the code

Then we began to write data set, we finally in the custom view data source and interface logic in the Activity, so we have to put through the interface or abstract class to callback, I here directly with the abstract classes to solve, before writing an abstract class, we think, we need what abstract class, First of all, there must be entity classes in the left and right columns, list collection in the right and left columns, item layout in the left and right columns of Recyclerview, item click events in the left and right columns, in addition, the item on the left will generally have the effect of clicking color, so we need to change the color before and after clicking. So we need ways to change left, right, back and forth, etc., so I wrote the following:

public abstract class BindData {
    public  abstract int getLeftLayoutId();
    public  abstract List getLeftData();
    public  abstract void bindLeftView(BaseViewAdapter.BaseHolder holder, int position, T bean);
    public abstract void bindDefaultStatus(BaseViewAdapter.BaseHolder holder, int position,T bean);
    public abstract void bindSelectStatus(BaseViewAdapter.BaseHolder holder, int position, T bean);
    public   abstract int getRightLayoutId();
    public abstract void bindRightView(BaseViewAdapter.BaseHolder holder, int position, E bean);
    public  abstract List getRightData();
    public abstract void rightItemClickListener(BaseViewAdapter.BaseHolder holder, int position,E bean);
   
}Copy the code

Viewfroup = viewFroup = viewFroup = viewFroup = viewfroup = viewfroup = viewfroup

First of all, we now carry out setAdapter for around RecyclerView:

public void setData(final BindData data) { this.data = data; rightView.addItemDecoration(new MeiTuanItem(context, data)); leftAdapter = new BaseViewAdapter(context, data.getLeftData()) { @Override protected void bind(final BaseHolder holder, final int position) { data.bindLeftView(holder, position, data.getLeftData().get(position)); if (position == getSelectPosition()) { data.bindSelectStatus(holder, position, data.getLeftData().get(position)); } else { data.bindDefaultStatus(holder, position, data.getLeftData().get(position)); } holder.itemView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { } }); } @Override public int getLayoutId() { return data.getLeftLayoutId(); }}; leftView.setAdapter(leftAdapter); leftView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); int lastPosition = ((LinearLayoutManager) recyclerView.getLayoutManager()).findLastVisibleItemPosition(); If (lastPosition = = data. GetLeftData (). The size () - 1) {leftView. SmoothScrollBy (50, 50); }}}); rightAdapter = new BaseViewAdapter(context, data.getRightData()) { @Override protected void bind(final BaseHolder holder, final int position) { data.bindRightView(holder, position, data.getRightData().get(position)); holder.itemView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { data.rightItemClickListener(holder, position, data.getRightData().get(position)); }}); } @Override public int getLayoutId() { return data.getRightLayoutId(); }}; rightView.setAdapter(rightAdapter); }Copy the code

This completes the basic layout. If you read the ZTX blog you should know how to write this MeiTuanItem. Here I post my code where the logic goes to recharge beliefs:

public class MeiTuanItem extends RecyclerView.ItemDecoration {
    private int mTitleHeight;
    private BindData data;
    private Paint mPaint;
    private Rect mBounds;
    private int backgroundColor;
    private int textColor;
    private int textSize;
    public  MeiTuanItem(Context context,int mTitleHeight, BindData data, int backgroundColor, int textColor,int textSize){
        this.mTitleHeight= dip2px(context,mTitleHeight);
        this.data=data;
        mPaint=new Paint();
        mBounds=new Rect();
        this.backgroundColor=backgroundColor;
        this.textColor=textColor;
        this.textSize= sp2px(context,textSize);

    }
    public  MeiTuanItem(Context context,BindData data){
        this(context, dip2px(context,10),data, Color.LTGRAY,Color.DKGRAY, sp2px(context,9));
    }

    @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDrawOver(c, parent, state);
        if(parent.getLayoutManager() instanceof LinearLayoutManager){
            int position = ((LinearLayoutManager) parent.getLayoutManager()).findFirstVisibleItemPosition();
            View itemView = parent.findViewHolderForAdapterPosition(position).itemView;
            boolean flag=false;
            if(position+1Copy the code



The rest is the story of left and right linkage. In fact, the final boss of this copy is also very simple. We know that when clicking on the left side, we need to make the clicked item change color and the recyclerView slide to the specified position on the right. When the recyclerView tag is equal to the position of the first item of recyclerView that is equal to the tag, we write a method to obtain the distance that recyclerView should slide:

public int leftBoundRightPosition(int position) { rightData=data.getRightData(); for (int i=0; iCopy the code

Here we return the position of the first position, so we can happily click:

pre name="code" class="java" style="display: none;">  holder.itemView.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        setSelectPosition(position);
                        ((LinearLayoutManager) rightView.getLayoutManager()).scrollToPositionWithOffset(leftBoundRightPosition(position),0);
                        //rightView.scrollToPosition(leftBoundRightPosition(position));
                        //((LinearLayoutManager) rightView.getLayoutManager()).smoothScrollToPosition(rightView,null,position);
                    }
                });Copy the code


Comment part of the code we can try, maybe I just deliberately waste your time!

And then we need to get the event when the right slides and the left slides, so we're going to listen for the right slides, and when the right tag is equal to the left tag, the left tag is going to scroll, and notice that if we're scrolling up and down, When we scroll down, we should change the critical point between the current tag and the next tag; when we scroll up, we should change the critical point between the current tag and the last tag. Therefore, we should separate judgment. First, we write several methods:

public boolean isTopNotEqualsBefore(int position) { if (data.getRightData().get(position) instanceof BaseMeiTuanBean && data.getRightData().get(position - 1) instanceof BaseMeiTuanBean) { return ((BaseMeiTuanBean) data.getRightData().get(position)).tagStr().equals(((BaseMeiTuanBean) data.getRightData().get(position - 1)).tagStr()); } return true; } public boolean isTopNotEqualsNext(int position) { if (data.getRightData().get(position) instanceof BaseMeiTuanBean && data.getRightData().get(position + 1) instanceof BaseMeiTuanBean) { return ((BaseMeiTuanBean) data.getRightData().get(position)).tagStr().equals(((BaseMeiTuanBean) data.getRightData().get(position + 1)).tagStr()); } return true; } public int rightBoundLeftPosition(int position) { leftData=data.getLeftData(); for (int i=0; iCopy the code

The first two methods are used to determine whether the tag is equal when it reaches the critical point when scrolling up and down. The third method is the same as the last one to obtain the position of recyclerView to be rolled to.

Then we implement scrolllistener:

rightView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); int position = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition(); //Log.e("dy:","dy:"+dy); Log.e("position:", "position:" + position); int lastPosition = ((LinearLayoutManager) recyclerView.getLayoutManager()).findLastVisibleItemPosition(); Log.e("lastPosition:", "lastPosition:" + lastPosition); if (dy > 0) { if (position + 1 < data.getRightData().size()) { if (position == 0) { leftAdapter.setSelectPosition(0); } else { if (isTopNotEqualsNext(position)) { leftAdapter.setSelectPosition(rightBoundLeftPosition(position)); leftView.scrollToPosition(rightBoundLeftPosition(position)); Log.e("rightBoundLeftPosition:","rightBoundLeftPosition:"+rightBoundLeftPosition(position)); // ((LinearLayoutManager) leftView.getLayoutManager()).smoothScrollToPosition(leftView,null,data.rightBoundLeftPosition(data.getRightData().get(po sition),data.getLeftData())); } } } } else { if (position + 1 < data.getRightData().size()) { if (position == 0) { leftAdapter.setSelectPosition(0); } else { if (isTopNotEqualsBefore(position)) { leftAdapter.setSelectPosition(rightBoundLeftPosition(position)); leftView.scrollToPosition(rightBoundLeftPosition(position)); // ((LinearLayoutManager) leftView.getLayoutManager()).smoothScrollToPosition(leftView,null,data.rightBoundLeftPosition(data.getRightData().get(po sition),data.getLeftData())); }}}}}});Copy the code


Thus, we can perform happy play in our activity by declaring it directly in the activity XML:


    
Copy the code

Then process it in the activity:

public class A extends AppCompatActivity { private MeiTuanFoodView meituan; private List leftData=new ArrayList<>(); private List rightData=new ArrayList<>(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.a); meituan= (MeiTuanFoodView) findViewById(R.id.meituan); for (int i=0; i<20; i++){ LeftBean bean=new LeftBean(); bean.setTag("i:"+i); bean.setId(i); leftData.add(bean); } for (int i=0; i<20; i++){ for (int j=0; j<20; j++){ RightBean bean=new RightBean(); bean.setTag("i:"+i); bean.setText("j:"+j+" i:"+i); rightData.add(bean); } } meituan.setData(new BindData() { @Override public int getLeftLayoutId() { return R.layout.item; } @Override public List getLeftData() { return leftData; } @Override public void bindLeftView(BaseViewAdapter.BaseHolder holder, int position, LeftBean bean) { TextView tv=holder.findViews(R.id.tv); tv.setText(bean.getTag()); } @Override public void bindDefaultStatus(BaseViewAdapter.BaseHolder holder, int position, LeftBean bean) { TextView tv=holder.findViews(R.id.tv); tv.setTextColor(Color.BLACK); tv.setBackgroundColor(Color.WHITE); } @Override public void bindSelectStatus(BaseViewAdapter.BaseHolder holder, int position, LeftBean bean) { TextView tv=holder.findViews(R.id.tv); tv.setTextColor(Color.WHITE); tv.setBackgroundColor(Color.BLACK); } @Override public int getRightLayoutId() { return R.layout.item; } @Override public void bindRightView(BaseViewAdapter.BaseHolder holder, int position, RightBean bean) { TextView tv=holder.findViews(R.id.tv); tv.setText(bean.getText()); } @Override public List getRightData() { return rightData; } @Override public void rightItemClickListener(BaseViewAdapter.BaseHolder holder, int position, RightBean bean) { Toast.makeText(A.this,bean.getText(),Toast.LENGTH_SHORT).show(); }}); }}Copy the code

As simple as that, our custom view has been written, and we only need to disclose these methods to basically realize the structure of the interface. Of course, there are many details worth optimizing in the actual project, but should I care, Anyway, I just in the happy loading force ~ finally on the effect and then ran to have a happy lunch on October 25, 2016 11:58:15!!!!