自定义组件:实现adapter动态管理子项,view高度自适应子项。可左右滑动,view中心位置为选中位。滑动到中间区域的子项自动放大高亮,其他缩小半透明。滑动手势结束可以惯性滑动到最近的子项选中状态。左右边界允许拉伸最多一个子项的距离,松开自动回弹到选中位置。
实现思路:
(思路来源参考goeasyway)
1,onMeasure (measureChild)循环测量子view (如需自适应大小可以在 测量完child后通过。setMeasuredDimension设置)
onLayout 获取子view 宽高 通过自己的布局逻辑确定每个child的位置(childView.layout) 计算 viewgroup的总宽高
2, onInterceptTouchEvent 在合适的时机返回true 消费事件
3,onTouchEvent 中处理触摸滑动方向及距离,通过scrollTo scrollBy 配合
在ACTION_UP 计算当前显示的item,并处理惯性
4,new Scroller(context) 使用scroller 实现惯性滑动过程
调用startScroll设置开始惯性滑动 invalidate()刷新界面
重写computeScroll()实现惯性并处理其他逻辑
5,removeAllViewsInLayout/addViewInLayout 使用等配合实现adapter动态添加更改child
实现代码:
一:使用
<com.sinovatio.demo.CoverflowView
android:id="@+id/c_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</com.sinovatio.demo.CoverflowView>
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CoverflowView coverflowView = findViewById(R.id.coverflowview);
coverflowView.setAdapter(new MyAdapter());
coverflowView.setSelection(3);
}
private class MyAdapter extends BaseAdapter {
@Override
public int getCount() {
return 5;
}
@Override
public Object getItem(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
if (convertView == null) {
view = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_layout, parent,false);
} else {
view = convertView;
}
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i("computeScroll",System.currentTimeMillis()+"");
}
});
return view;
}
}
二:viewGroup
public class CoverflowView extends ViewGroup {
private int touchSlop;
private float xLastDown;
private float xLastMove;
private int leftBorder;
private int rightBorder;
private int childWidth;
private int maxHeight=0;
private Scroller scroller;
private Adapter adapter;
private int selection = 0;
private int viewWidth=0;
public CoverflowView(Context context) {
this(context, null);
}
public CoverflowView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CoverflowView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
touchSlop = ViewConfiguration.get(context).getScaledPagingTouchSlop();
scroller = new Scroller(context);
}
public void setAdapter(Adapter adapter) {
this.adapter = adapter;
reset();
}
public Adapter getAdapter() {
return adapter;
}
public void setSelection(int selection) {
if (adapter == null) {
throw new IllegalStateException("Error: Adapter is null.");
}
if (selection < 0 || selection > (adapter.getCount() - 1)) {
throw new IllegalArgumentException("Position index must be in range of adapter values (0 - getCount()-1)");
}
this.selection = selection;
reset();
}
public int getSelection() {
return selection;
}
private void reset() {
if (adapter == null || adapter.getCount() == 0) {
return;
}
removeAllViewsInLayout();
// removeAllViews();
int count = adapter.getCount();
for (int i = 0; i < count; i++) {
View view = adapter.getView(i, null, this);
/**
* view.getLayoutParams() new LayoutParams(320, 320)
* 自适应高度需要获取子项的LayoutParams,就需要子项添加是使用当前为parent。否则或报错
*/
addViewInLayout(view, 0, view.getLayoutParams(), true);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
viewWidth = w;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View v = getChildAt(i);
//测量子view 使其知道自己的大小
measureChild(v, widthMeasureSpec, heightMeasureSpec);
//此时才可以得到其大小
int height = v.getMeasuredHeight();
if(maxHeight<=height){
maxHeight=height;
}
// viewWidth+=v.getMeasuredWidth();
}
//自适应高度
setMeasuredDimension(widthMeasureSpec,maxHeight+14);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int painterPosX = l;
int painterPosY = t;
int childCount = getChildCount();
// 使用了比较简单直接的做法(当然不是最优化的做法),直接水平线性布局所有的子View
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
int width = childView.getMeasuredWidth();
int height = childView.getMeasuredHeight();
//为子view布局
childView.layout(painterPosX, painterPosY, painterPosX + width, painterPosY + height);
painterPosX += width;
childWidth = width;
}
leftBorder = l - viewWidth / 2;
rightBorder = painterPosX ;
scrollToSelectionCenter();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// Log.i("computeScroll4",System.currentTimeMillis()+"");
xLastDown = ev.getRawX();
break;
case MotionEvent.ACTION_MOVE:
// Log.i("computeScroll3",System.currentTimeMillis()+"");
xLastMove = ev.getRawX();
float deltaX = xLastMove - xLastDown;
//在发生滑动是拦截事件交给自己onTouchEvent消费
if (Math.abs(deltaX) > touchSlop) {
return true;
}
break;
case MotionEvent.ACTION_UP:
// Log.i("computeScroll0",System.currentTimeMillis()+"");
break;
}
// Log.i("computeScroll2",System.currentTimeMillis()+"");
//其他时间不对事件处理交给child
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
float xMove = event.getRawX();
int deltaX = (int) (xLastMove - xMove);
if ((getScrollX() + deltaX) < leftBorder) {
scrollTo(leftBorder, 0);
} else if ((getScrollX() + childWidth + deltaX) > rightBorder) {
scrollTo(rightBorder - childWidth, 0);
}
scrollBy(deltaX, 0); // 你不得不自己试一下scrollBy与scrollTo的区别。
xLastMove = xMove;
updateSelection();
break;
case MotionEvent.ACTION_UP:
// Log.i("computeScroll",scroller.computeScrollOffset()+"");
scrollToSelectionCenter();
break;
}
return super.onTouchEvent(event);
}
/**
* 根据当间移动的位置确定被先中的Items
*/
private void updateSelection() {
selection = (getScrollX() + viewWidth / 2) / childWidth;
if (selection < 0) {
selection = 0;
} else if (selection > adapter.getCount() - 1) {
selection = adapter.getCount() - 1;
}
}
/**
* 将选中的Item在ViewGroup的中央显示
*/
private void scrollToSelectionCenter() {
if (adapter == null || adapter.getCount() == 0) {
return;
}
if (selection < 0 || selection > adapter.getCount() - 1) {
return;
}
View viewChild = getChildAt(selection);
int dx = (selection * childWidth + childWidth / 2) - getScrollX() - viewWidth / 2;
//开始惯性滑动
scroller.startScroll(getScrollX(), 0, dx, 0);
invalidate();
}
@Override
public void computeScroll() {
//检查滑动是否结束
if (scroller.computeScrollOffset()) {
scrollTo(scroller.getCurrX(), scroller.getCurrY());
// Log.i("computeScroll",scroller.getCurrX()+" "+scroller.getCurrY());
invalidate();
}
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
if (i == selection) {
view.setScaleX(1f);
view.setScaleY(1f);
view.setAlpha(1f);
} else { // 没有选中的Item缩小并变半透明
view.setScaleX(0.75f);
view.setScaleY(0.75f);
view.setAlpha(0.35f);
}
}
}
}