各位看官,下面是效果圖(自己手殘打了一下碼),請先過目:
簡單的說一下效果圖,橫向來區分的話可以把UI圖分爲三部分【左邊是輪盤 , 中間是弧形列表 也是一級菜單 , 右邊是列表是二級菜單(不重要沒有圖)】:
要求:
- 實現一個圍繞輪盤的弧形列表;
- 弧形列表滑動後自動選中居中的條目,然後更新右邊的二級菜單;
- 弧形列表點擊後自動滑動到居中位置並選中,然後更新右邊的二級菜單;
- 弧形列表所有條目都可以選擇;
- 左邊的輪盤跟隨弧形列表旋轉;
好的、需求很明確,一級菜單弧形列表可滑可點自動選中,輪盤跟隨旋轉;
第一步:先實現弧形列表;
然後開始找輪子,各大網站找個一圈都沒有發現類似的開源框架;發現這個'https://github.com/kHRYSTAL/CircleRecyclerView' 可以實現弧形列表,下載下來試了一下有些差別,不能所有的條目都選中(第一個條目拉不下來),未選中的沒有點擊事件;後續又找了幾個類似的文章,跟效果都有點差別,無奈只能自己寫了;
實現弧形列表的第一想法時自定義RecyclerView的LayoutManager,後來參考了一下這篇文章(https://github.com/dkmeteor/CircleList)用設置偏移量實現RecyclerView弧形列表;
實現方式一 ,矩陣偏移:
1、先自定義Layout實現偏移 , RecyclerView的Item佈局的根佈局使用這個Layout;
public class MatrixTranslateLayout extends LinearLayout {
private int parentHeight = 0;
private int topOffset = 0;
public MatrixTranslateLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setParentHeight(int height) {
parentHeight = height;
}
@Override
protected void dispatchDraw(Canvas canvas) {
canvas.save();
if (topOffset == 0) {
topOffset = getHeight() / 2;
}
int top = getTop()+topOffset;
float tran = calculateTranslate(top , parentHeight);
Matrix m = canvas.getMatrix();
m.setTranslate(tran,0);
canvas.concat(m);
super.dispatchDraw(canvas);
canvas.restore();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private float calculateTranslate(int top, int h) {
float result = 0f;
int hh = h/2;
result = Math.abs(top - hh)*-1;
return (float) (result/2);
}
}
2、RecyclerView的Adapter填充佈局時設置矩陣偏移量;
class MAdapter extends RecyclerView.Adapter {
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new MAdapter.VH(LayoutInflater.from(ArcActivity.this).inflate(R.layout.item_arc, parent, false));
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
MAdapter.VH vh = (MAdapter.VH) holder;
((MatrixTranslateLayout) vh.itemView).setParentHeight(recyclerView.getHeight());
vh.tv.setText(mDatas.get(position));
final int fp = position;
vh.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TUtils.show(ArcActivity.this, "點擊" +mDatas.get(fp));
}
});
}
@Override
public int getItemCount() {
return mDatas.size();
}
class VH extends RecyclerView.ViewHolder {
public TextView tv;
public VH(@NonNull View itemView) {
super(itemView);
tv = itemView.findViewById(R.id.tv);
}
}
}
切記:要等佈局完全加載後再設置Adapter , 不然會拿不到RecyclerView的高度;
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
findView();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
}
});
3、當RecyclerView滑動時重新調整矩陣偏移量;
private void findView() {
mAdapter = new MAdapter();
recyclerView.setAdapter(mAdapter);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
for (int i = 0; i < recyclerView.getChildCount(); i++) {
recyclerView.getChildAt(i).invalidate();
}
}
});
}
這樣一個弧形RecyclerView就實現了;
實現方式二 ,內邊距偏移:
不需要自定義Layout , 只需要改變Item根佈局的PaddingLift即可;
根據方式一的思路換了一個實現方式,具體看代碼:
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
recyclerViewHeight = recyclerView.getHeight();
setData();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
}
});
private void setData() {
recyclerView.setAdapter(new RecyclerView.Adapter() {
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new VH(LayoutInflater.from(PadingArcActivity.this).inflate( R.layout.item_pading,parent,false));
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
VH vh = (VH) holder;
RelativeLayout mv = (RelativeLayout) vh.itemView;
mv.setPadding(calculateTranslate(mv.getTop() , recyclerViewHeight) , 0 ,0 ,0 );
vh.tv.setText("你好"+position+"索引");
final int fp = position;
vh.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TUtils.show(PadingArcActivity.this , "你好"+fp+"索引");
}
});
}
@Override
public int getItemCount() {
return 100;
}
class VH extends RecyclerView.ViewHolder{
public TextView tv;
public VH(@NonNull View itemView) {
super(itemView);
tv = itemView.findViewById(R.id.tv);
}
}
});
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
for (int i = 0; i < recyclerView.getChildCount(); i++) {
int pad = calculateTranslate(recyclerView.getChildAt(i).getTop() , recyclerViewHeight);
recyclerView.getChildAt(i).setPadding( pad, 0 ,0 ,0 );
}
}
});
}
private int calculateTranslate(int top, int h) {
int result = 0;
h = h - UiUtils.dip2px(this , 60); //減去當前控件的高度,(60是已知當前Item的高度)
int hh = h/2;
result = Math.abs(hh - top);
result = hh - result;
return result/2;
}
這樣通過改變內邊距的方式也可以實現弧形列表;
佈局這種小兒科的內容就不貼出來了,下面是弧形列表實現後的效果;
Item的View就只寫了一個TextView , 看着比較粗糙;