目前項(xiàng)目中有個功能類似淘寶的淘搶購界面,橫向的recyclerview晦炊,滾動后始終固定中間位置栈暇,點(diǎn)擊后也固定到中間位置。首先先感謝一下提供思路的這位朋友渐夸,Android橫向滑動自動選中控件嗤锉。
下面看一下效果。一墓塌、自定義橫向滑動的Recyclerview
public class AutoLocateHorizontalView extends RecyclerView {
/**
* 一個屏幕中顯示多少個item瘟忱,必須為奇數(shù)
*/
private int itemCount = 5;
/**
* 初始時選中的位置
*/
private int initPos = 0;
private int deltaX;
private WrapperAdapter wrapAdapter;
private Adapter adapter;
private LinearLayoutManager linearLayoutManager;
private boolean isInit;
private OnSelectedPositionChangedListener listener;
private boolean isFirstPosChanged = true; //剛初始化時是否觸發(fā)位置改變的監(jiān)聽
private int oldSelectedPos = initPos; //記錄上次選中的位置
/**
* 當(dāng)前被選中的位置
*/
private int selectPos = initPos;
private Scroller mScroller;
/**
* 當(dāng)要調(diào)用moveToPosition()方法時要先記錄已經(jīng)移動了多少位置
*/
private int oldMoveX;
private boolean isMoveFinished = true;
public AutoLocateHorizontalView(Context context) {
super(context);
}
public AutoLocateHorizontalView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public AutoLocateHorizontalView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private void init() {
mScroller = new Scroller(getContext());
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (isInit) {
if (initPos >= adapter.getItemCount()) {
initPos = adapter.getItemCount() - 1;
}
if (isFirstPosChanged && listener != null) {
listener.selectedPositionChanged(initPos);
}
linearLayoutManager.scrollToPositionWithOffset(0, -initPos * (wrapAdapter.getItemWidth()));
isInit = false;
}
}
});
}
/**
* 設(shè)置初始化時選中的位置,該方法必須在{@link AutoLocateHorizontalView#setAdapter(Adapter) }之前調(diào)用
*
* @param initPos 初始位置奥额,如果位置超過了item的數(shù)量則默認(rèn)選中最后一項(xiàng)item
*/
public void setInitPos(int initPos) {
if (adapter != null) {
throw new RuntimeException("This method should be called before setAdapter()!");
}
this.initPos = initPos;
selectPos = initPos;
oldSelectedPos = initPos;
}
/**
* 設(shè)置每次顯示多少個item,該方法必須在{@link AutoLocateHorizontalView#setAdapter(Adapter) }之前調(diào)用
*
* @param itemCount 必須為奇數(shù),否則默認(rèn)會設(shè)置成小于它的最大奇數(shù)
*/
public void setItemCount(int itemCount) {
if (adapter != null) {
throw new RuntimeException("This method should be called before setAdapter()!");
}
if (itemCount % 2 == 0) {
this.itemCount = itemCount - 1;
} else {
this.itemCount = itemCount;
}
}
/**
* 刪除item后偏移距離可能需要重新計算酷誓,從而保證selectPos的正確
*
* @param adapter
*/
private void correctDeltax(Adapter adapter) {
if (adapter.getItemCount() <= selectPos) {
deltaX -= wrapAdapter.getItemWidth() * (selectPos - adapter.getItemCount() + 1);
}
calculateSelectedPos();
}
/**
* 刪除時選中的數(shù)據(jù)發(fā)生改變披坏,要重新回調(diào)方法
*
* @param startPos
*/
private void reCallListenerWhenRemove(int startPos) {
if (startPos <= selectPos && listener != null) {
correctDeltax(adapter);
listener.selectedPositionChanged(selectPos);
} else {
correctDeltax(adapter);
}
}
/**
* 添加數(shù)據(jù)時選中的數(shù)據(jù)發(fā)生改變态坦,要重新回調(diào)方法
*
* @param startPos
*/
private void reCallListenerWhenAdd(int startPos) {
if (startPos <= selectPos && listener != null) {
listener.selectedPositionChanged(selectPos);
}
}
/**
* 當(dāng)使用整體刷新時要重新回調(diào)方法
*/
private void reCallListenerWhenChanged() {
if (listener != null) {
listener.selectedPositionChanged(selectPos);
}
}
@Override
public void setAdapter(final Adapter adapter) {
this.adapter = adapter;
this.wrapAdapter = new WrapperAdapter(adapter, getContext(), itemCount);
adapter.registerAdapterDataObserver(new AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
wrapAdapter.notifyDataSetChanged();
reCallListenerWhenChanged();
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
wrapAdapter.notifyDataSetChanged();
reCallListenerWhenAdd(positionStart);
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
wrapAdapter.notifyDataSetChanged();
reCallListenerWhenRemove(positionStart);
}
});
deltaX = 0;
if (linearLayoutManager == null) {
linearLayoutManager = new LinearLayoutManager(getContext());
}
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
super.setLayoutManager(linearLayoutManager);
super.setAdapter(this.wrapAdapter);
isInit = true;
}
@Override
public void setLayoutManager(LayoutManager layout) {
if (!(layout instanceof LinearLayoutManager)) {
throw new IllegalStateException("The LayoutManager here must be LinearLayoutManager!");
}
this.linearLayoutManager = (LinearLayoutManager) layout;
}
@Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
if (state == SCROLL_STATE_IDLE) {
if (wrapAdapter == null) {
return;
}
int itemWidth = wrapAdapter.getItemWidth();
int headerFooterWidth = wrapAdapter.getHeaderFooterWidth();
if (itemWidth == 0 || headerFooterWidth == 0) {
//此時adapter還沒有準(zhǔn)備好盐数,忽略此次調(diào)用
return;
}
//超出上個item的位置
int overLastPosOffset = deltaX % itemWidth;
if (overLastPosOffset == 0) {
//剛好處于一個item選中位置,無需滑動偏移糾正
} else if (Math.abs(overLastPosOffset) <= itemWidth / 2) {
scrollBy(-overLastPosOffset, 0);
} else if (overLastPosOffset > 0) {
scrollBy((itemWidth - overLastPosOffset), 0);
} else {
scrollBy(-(itemWidth + overLastPosOffset), 0);
}
calculateSelectedPos();
//此處通知刷新是為了重新繪制之前被選中的位置以及剛剛被選中的位置
wrapAdapter.notifyItemChanged(oldSelectedPos + 1);
wrapAdapter.notifyItemChanged(selectPos + 1);
oldSelectedPos = selectPos;
if (listener != null) {
listener.selectedPositionChanged(selectPos);
}
}
}
public void moveToPosition(int position) {
if(position < 0 || position > adapter.getItemCount() - 1){
throw new IllegalArgumentException("Your position should be from 0 to "+(adapter.getItemCount()-1));
}
oldMoveX = 0;
isMoveFinished = false;
int itemWidth = wrapAdapter.getItemWidth();
if (position != selectPos) {
int deltx = (position - selectPos) * itemWidth;
mScroller.startScroll(getScrollX(), getScrollY(), deltx, 0);
postInvalidate();
}
}
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
int x = mScroller.getCurrX() - oldMoveX;
oldMoveX += x;
scrollBy(x, 0);
} else if (mScroller.isFinished()) {
//此處通知刷新是為了重新繪制之前被選中的位置以及剛剛被選中的位置
if (isMoveFinished) {
return;
}
wrapAdapter.notifyItemChanged(oldSelectedPos + 1);
wrapAdapter.notifyItemChanged(selectPos + 1);
oldSelectedPos = selectPos;
if (listener != null) {
listener.selectedPositionChanged(selectPos);
}
isMoveFinished = true;
}
}
@Override
public void onScrolled(int dx, int dy) {
super.onScrolled(dx, dy);
deltaX += dx;
calculateSelectedPos();
}
private void calculateSelectedPos() {
int itemWidth = wrapAdapter.getItemWidth();
if (deltaX > 0) {
if(itemWidth==0){
return;
}
selectPos = (deltaX) / itemWidth + initPos;
} else {
if(itemWidth==0){
return;
}
selectPos = initPos + (deltaX) / itemWidth;
}
}
class WrapperAdapter extends Adapter {
private Context context;
private Adapter adapter;
private int itemCount;
private static final int HEADER_FOOTER_TYPE = -1;
private View itemView;
/**
* 頭部或尾部的寬度
*/
private int headerFooterWidth;
/**
* 每個item的寬度
*/
private int itemWidth;
public WrapperAdapter(Adapter adapter, Context context, int itemCount) {
this.adapter = adapter;
this.context = context;
this.itemCount = itemCount;
if (adapter instanceof IAutoLocateHorizontalView) {
itemView = ((IAutoLocateHorizontalView) adapter).getItemView();
} else {
throw new RuntimeException(adapter.getClass().getSimpleName() + " should implements com.jianglei.view.AutoLocateHorizontalView.IAutoLocateHorizontalView !");
}
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == HEADER_FOOTER_TYPE) {
View view = new View(context);
headerFooterWidth = parent.getMeasuredWidth() / 2 - (parent.getMeasuredWidth() / itemCount) / 2;
LayoutParams params = new LayoutParams(headerFooterWidth, ViewGroup.LayoutParams.MATCH_PARENT);
view.setLayoutParams(params);
return new HeaderFooterViewHolder(view);
}
ViewHolder holder = adapter.onCreateViewHolder(parent, viewType);
itemView = ((IAutoLocateHorizontalView) adapter).getItemView();
int width = parent.getMeasuredWidth() / itemCount;
ViewGroup.LayoutParams params = itemView.getLayoutParams();
if (params != null) {
params.width = width;
itemWidth = width;
itemView.setLayoutParams(params);
}
return holder;
}
@SuppressWarnings("unchecked")
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
if (!isHeaderOrFooter(position)) {
adapter.onBindViewHolder(holder, position - 1);
if (selectPos == position - 1) {
((IAutoLocateHorizontalView) adapter).onViewSelected(true, position - 1, holder, itemWidth);
} else {
((IAutoLocateHorizontalView) adapter).onViewSelected(false, position - 1, holder, itemWidth);
}
}
}
@Override
public int getItemCount() {
return adapter.getItemCount() + 2;
}
@Override
public int getItemViewType(int position) {
if (position == 0 || position == getItemCount() - 1) {
return HEADER_FOOTER_TYPE;
}
return adapter.getItemViewType(position - 1);
}
private boolean isHeaderOrFooter(int pos) {
if (pos == 0 || pos == getItemCount() - 1) {
return true;
}
return false;
}
public int getHeaderFooterWidth() {
return headerFooterWidth;
}
public int getItemWidth() {
return itemWidth;
}
class HeaderFooterViewHolder extends ViewHolder {
HeaderFooterViewHolder(View itemView) {
super(itemView);
}
}
}
public interface IAutoLocateHorizontalView {
/**
* 獲取item的根布局
*/
View getItemView();
/**
* 當(dāng)item被選中時會觸發(fā)這個回調(diào)伞梯,可以修改被選中時的樣式
*
* @param isSelected 是否被選中
* @param pos 當(dāng)前view的位置
* @param holder
* @param itemWidth 當(dāng)前整個item的寬度
*/
void onViewSelected(boolean isSelected, int pos, ViewHolder holder, int itemWidth);
}
/***
* 選中位置改變時的監(jiān)聽
*/
public interface OnSelectedPositionChangedListener {
void selectedPositionChanged(int pos);
}
public void setOnSelectedPositionChangedListener(OnSelectedPositionChangedListener listener) {
this.listener = listener;
}
}
二玫氢、其他使用步驟和RecyclerView基本一致
1、初始化RecyclerView
2谜诫、實(shí)例化適配器
3漾峡、添加數(shù)據(jù)
recyclerview = ((AutoLocateHorizontalView) findViewById(R.id.recyclerViewId));
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerview.setHasFixedSize(true);
recyclerview.setLayoutManager(linearLayoutManager);
recyclerview.setOnSelectedPositionChangedListener(new AutoLocateHorizontalView.OnSelectedPositionChangedListener() {
@Override
public void selectedPositionChanged(int pos) {
Log.i("===位置位置====","fragment:滾動滾動:"+pos);
contentTv.setText("當(dāng)前位置下標(biāo):"+pos);
}
});
testAdapter = new TestAdapter(new ArrayList<TestEntity>(), this, new TestAdapter.OnItemClickListener() {
@Override
public void onItemClick(int position, View view) {
Log.i("===位置位置====","fragment:點(diǎn)擊點(diǎn)擊:"+position);
recyclerview.moveToPosition(position-1);
}
});
recyclerview.setInitPos(5);
recyclerview.setItemCount(5);
recyclerview.setAdapter(testAdapter);
模擬添加一些數(shù)據(jù)
List<String> list = new ArrayList<>();
List<String> list2 = new ArrayList<>();
list.add("20:00");
list.add("21:00");
list.add("09:00");
list.add("10:00");
list.add("12:00");
list.add("14:00");
list.add("16:00");
list.add("18:00");
list.add("20:00");
list2.add("昨日精選");
list2.add("昨日精選");
list2.add("昨日精選");
list2.add("搶購中");
list2.add("搶購中");
list2.add("搶購中");
list2.add("搶購中");
list2.add("預(yù)熱中");
list2.add("預(yù)熱中");
List<TestEntity> list333 = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
TestEntity en = new TestEntity(list.get(i),list2.get(i));
list333.add(en);
}
testAdapter.clear();
testAdapter.addAll(list333);