最近有点忙,项目进度跟的比较紧。最近需求那边让我们写一个左右和上下都可滑动的列表,用来展示多个Title的值。这里我把需求简化了一下。老规矩,先看图。
在看到需求的时候,有在网上看看有没有别人造好的轮子,找是找到了,但是它是用HorizontalScrollView
、ScrollView
、ListView
实现的,效果是有了,但是ListView没有复用了,导致我一次性加载800条数据时,界面卡顿,体验很不好。而且它的点击效果只能分别点击左边和右边,并不能点击的时候整个item
都高亮。
所以,这里我就去研究了一下该怎么实现这个需求。
这里我封装了一个HRecycleView
去继承RelativeLayout
。
分为上下两部分
① TitleLayout
包括左边的"名称"(固定不可滑动),右边的"Title"(多个可滑动)
② Title的数据
使用的是RecyclerView
这里只要处理水平方向的手势滑动即可,所以,我们需要去拦截手势,使用
scrollTo
方法实现水平滚动。请看代码的详细注释。
/**
* Created by chawei on 2018/4/29.
*/
public class HRecyclerView extends RelativeLayout {
//头部title布局
private LinearLayout mRightTitleLayout;
//手指按下时的位置
private float mStartX = 0;
//滑动时和按下时的差值
private int mMoveOffsetX = 0;
//最大可滑动差值
private int mFixX = 0;
//左边标题集合
private String[] mLeftTextList;
//左边标题的宽度集合
private int[] mLeftTextWidthList;
//右边标题集合
private String[] mRightTitleList = new String[]{};
//右边标题的宽度集合
private int[] mRightTitleWidthList = null;
//展示数据时使用的RecycleView
private RecyclerView mRecyclerView;
//RecycleView的Adapter
private Object mAdapter;
//需要滑动的View集合
private ArrayList mMoveViewList = new ArrayList();
private Context context;
//右边可滑动的总宽度
private int mRightTotalWidth = 0;
//右边单个view的宽度
private int mRightItemWidth = 60;
//左边view的宽度
private int mLeftViewWidth = 80;
//左边view的高度
private int mLeftViewHeight=40;
//触发拦截手势的最小值
private int mTriggerMoveDis=30;
public HRecyclerView(Context context) {
this(context, null);
}
public HRecyclerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public HRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
}
private void initView() {
LinearLayout linearLayout = new LinearLayout(getContext());
linearLayout.setOrientation(LinearLayout.VERTICAL);
linearLayout.addView(createHeadLayout());
linearLayout.addView(createMoveRecyclerView());
addView(linearLayout, new LayoutParams(LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
}
/**
* 创建头部布局
* @return
*/
private View createHeadLayout() {
LinearLayout headLayout = new LinearLayout(getContext());
headLayout.setGravity(Gravity.CENTER);
LinearLayout leftLayout = new LinearLayout(getContext());
addListHeaderTextView(mLeftTextList[0], mLeftTextWidthList[0], leftLayout);
leftLayout.setGravity(Gravity.CENTER);
headLayout.addView(leftLayout, 0, new ViewGroup.LayoutParams(dip2px(context, mLeftViewWidth), dip2px(context, mLeftViewHeight)));
mRightTitleLayout = new LinearLayout(getContext());
for (int i = 0; i < mRightTitleList.length; i++) {
addListHeaderTextView(mRightTitleList[i], mRightTitleWidthList[i], mRightTitleLayout);
}
headLayout.addView(mRightTitleLayout);
return headLayout;
}
/**
* 创建数据展示布局
* @return
*/
private View createMoveRecyclerView() {
RelativeLayout linearLayout = new RelativeLayout(getContext());
mRecyclerView = new RecyclerView(getContext());
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(layoutManager);
if(null !=mAdapter){
if (mAdapter instanceof CommonAdapter) {
mRecyclerView.setAdapter((CommonAdapter) mAdapter);
mMoveViewList = ((CommonAdapter) mAdapter).getMoveViewList();
}
}
linearLayout.addView(mRecyclerView, new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
return linearLayout;
}
/**
* 设置adapter
* @param adapter
*/
public void setAdapter(Object adapter) {
mAdapter = adapter;
initView();
}
/**
* 设置头部title单个布局
* @param headerName
* @param width
* @param leftLayout
* @return
*/
private TextView addListHeaderTextView(String headerName, int width, LinearLayout leftLayout) {
TextView textView = new TextView(getContext());
textView.setText(headerName);
textView.setGravity(Gravity.CENTER);
leftLayout.addView(textView, width, dip2px(context, 50));
return textView;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mStartX = ev.getX();
break;
case MotionEvent.ACTION_MOVE:
int offsetX = (int) Math.abs(ev.getX() - mStartX);
if (offsetX > mTriggerMoveDis) {//水平移动大于30触发拦截
return true;
} else {
return false;
}
}
return super.onInterceptTouchEvent(ev);
}
/**
* 右边可滑动的总宽度
* @return
*/
private int rightTitleTotalWidth() {
if (0 == mRightTotalWidth) {
for (int i = 0; i < mRightTitleWidthList.length; i++) {
mRightTotalWidth = mRightTotalWidth + mRightTitleWidthList[i];
}
}
return mRightTotalWidth;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
return true;
case MotionEvent.ACTION_MOVE:
int offsetX = (int) Math.abs(event.getX() - mStartX);
if (offsetX > 30) {
mMoveOffsetX = (int) (mStartX - event.getX() + mFixX);
if (0 > mMoveOffsetX) {
mMoveOffsetX = 0;
} else {
//当滑动大于最大宽度时,不在滑动(右边到头了)
if ((mRightTitleLayout.getWidth() + mMoveOffsetX) > rightTitleTotalWidth()) {
mMoveOffsetX = rightTitleTotalWidth() - mRightTitleLayout.getWidth();
}
}
//跟随手指向右滚动
mRightTitleLayout.scrollTo(mMoveOffsetX, 0);
if (null != mMoveViewList) {
for (int i = 0; i < mMoveViewList.size(); i++) {
//使每个item随着手指向右滚动
mMoveViewList.get(i).scrollTo(mMoveOffsetX, 0);
}
}
}
break;
case MotionEvent.ACTION_UP:
mFixX = mMoveOffsetX; //设置最大水平平移的宽度
break;
}
return super.onTouchEvent(event);
}
/**
* 列表头部数据
* @param headerListData
*/
public void setHeaderListData(String[] headerListData) {
mRightTitleList = headerListData;
mRightTitleWidthList = new int[headerListData.length];
for (int i = 0; i < headerListData.length; i++) {
mRightTitleWidthList[i] = dip2px(context, mRightItemWidth);
}
mLeftTextWidthList = new int[]{dip2px(context, mLeftViewWidth)};
mLeftTextList = new String[]{"名称"};
}
}
这里模拟了一次加载1W条数据,没有卡顿效果。
/**
* Created by chawei on 2018/4/29.
*/
public class CoinActivity extends AppCompatActivity {
private ArrayList mDataModels;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_stock_layout);
HRecyclerView hRecyclerView= (HRecyclerView) findViewById(R.id.id_hrecyclerview);
mDataModels = new ArrayList<>();
for(int i=0;i<10000;i++) {
CoinInfo coinInfo = new CoinInfo();
coinInfo.name = "USDT";
coinInfo.priceLast="20.0";
coinInfo.riseRate24="0.2";
coinInfo.vol24="10020";
coinInfo.close="22.2";
coinInfo.open="40.0";
coinInfo.bid="33.2";
coinInfo.ask="19.0";
coinInfo.amountPercent = "33.3%";
mDataModels.add(coinInfo);
}
hRecyclerView.setHeaderListData(getResources().getStringArray(R.array.right_title_name));
CoinAdapter adapter = new CoinAdapter(this, mDataModels, R.layout.item_layout, new CommonViewHolder.onItemCommonClickListener() {
@Override
public void onItemClickListener(int position) {
Toast.makeText(CoinActivity.this, "position--->"+position, Toast.LENGTH_SHORT).show();
}
@Override
public void onItemLongClickListener(int position) {
}
});
hRecyclerView.setAdapter(adapter);
}
}
这里封装了
RecyclerView
的通用Adapter
和ViewHolder
。所以如果要使用HRecyclerView
的setAdapter
就必须继承封装的通用Adapter
。
CommonAdapter
和CommonViewHolder
请点击demo地址查看
GitHubDemo地址