很多音乐播放器如qq音乐,kugou音乐等都有一个专辑推荐的那个横幅,它扩展了软件的空间,也为用户带来了更好的交互感受。
在此,我也模仿着实现了此效果,不足之处请大家见谅,欢迎提出问题,和大家一起学习。
我给他取名叫【BannerLayout】,主要是觉得它也如其他layout特性差不多吧。
public class BannerLayout extends ViewGroup { public BannerLayout(Context context) { super(context); // TODO Auto-generated constructor stub } public BannerLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub } public BannerLayout(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub } }
BannerLayout 继承与 ViewGroup.
这个BannerLayout可以自动滚动,所以我们需要一个滚动器Scroller .
this.scroller = new Scroller(context, new DecelerateInterpolator(2));
我给scroller设置了一个插值器DecelerateInterpolator,就可以再滚动的时候实现滚动速度是中间快,开始和结束的时候慢的效果,个人觉得这个效果显得比较优雅。
要实现自动滚动,因此,我们可以使用handler还帮我们起到计时的作用。此外也可以使用timer等其他方式。
private Handler handler=new Handler() { @Override public void handleMessage(Message msg) {
//autoScroll是一标志boolean亮,用来标志是否需要滚动,因此就能手动控制其滚动了,
if(autoScroll && currentWhat==msg.what) { currentScreenIndex=(currentScreenIndex+1)%getChildCount(); scrollToScreen(currentScreenIndex); Log.i("TAG","handleMessage scrollToScreen:"+currentScreenIndex); if(autoScroll)//给自身发送延时消息, handler.sendEmptyMessageDelayed(currentWhat, scrollTime); } } };
由于BannerLayout里面的子元素如图片都是水平布局的,所以我们需要手动控制它们在此布局的位置了,
重写onMeasure函数:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // super.onMeasure(widthMeasureSpec, heightMeasureSpec); int maxHeight=-1; final int count = getChildCount(); for (int i = 0; i < count; i++) { getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec); maxHeight=Math.max(maxHeight, getChildAt(i).getMeasuredHeight()); }
// maxHeight=Math.min(maxHeight, MeasureSpec.getSize(heightMeasureSpec)); Log.e("TAG","onMeasure Height:"+maxHeight); setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),maxHeight); }
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { final int count = getChildCount(); int cLeft = 0; for (int i = 0; i < count; i++) { View child = getChildAt(i); if (child.getVisibility() == View.GONE) continue; // child.setVisibility(View.VISIBLE); final int childWidth = child.getMeasuredWidth(); child.layout(cLeft, 0, cLeft +childWidth, child.getMeasuredHeight()); cLeft += childWidth; } }
此外,我们需要处理用户触摸事件,实现用户按下的时候停止滚动,用户拖动的时候能够随之移动,
@Override public boolean onTouchEvent(MotionEvent ev) { if (getChildCount() == 0) return false; final int action = ev.getAction(); final float x = ev.getX(); switch (action) { case MotionEvent.ACTION_DOWN: autoScroll=false; currentWhat++; mLastMotionX = x; if (!scroller.isFinished()) { scroller.abortAnimation(); } // Log.i("TAG","ACTION_DOWN"); return true; case MotionEvent.ACTION_MOVE: final int deltaX = (int) (mLastMotionX - x); // boolean xMoved = Math.abs(deltaX) > mTouchSlop; mLastMotionX = x; if((0==currentScreenIndex && deltaX<0) || (getChildCount()-1==currentScreenIndex && deltaX>0)) scrollBy(deltaX/4, 0);//此处实现了越界时候的阻尼效果 else // Log.i("TAG","ACTION_MOVE"); // if (xMoved) scrollBy(deltaX, 0); final int screenWidth = getWidth(); currentScreenIndex=(getScrollX() + (screenWidth / 2))/ screenWidth; return true; case MotionEvent.ACTION_UP: snapToDestination(); if(!autoScroll) { autoScroll=true; handler.sendEmptyMessageDelayed(currentWhat, scrollTime); } break; case MotionEvent.ACTION_CANCEL: snapToDestination(); if(!autoScroll) { autoScroll=true; handler.sendEmptyMessageDelayed(currentWhat, scrollTime); } } return false; }
接下来,我们需要实现自定滚动到某一屏的效果:
private void scrollToScreen(int whichScreen) { // if (!scroller.isFinished()) // return; // Log.e("TAG","scrollToScreen:"+whichScreen); int delta = 0; delta = whichScreen * getWidth() - getScrollX(); // scroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2); scroller.startScroll(getScrollX(), 0, delta, 0, 1500); invalidate(); currentScreenIndex=whichScreen; } private void snapToDestination() { final int x=getScrollX(); final int screenWidth = getWidth(); scrollToScreen((x + (screenWidth / 2))/ screenWidth); }
差不多就这些了,其实挺简单的,哈哈
下面是全部代码:
public class BannerLayout extends ViewGroup { private Scroller scroller; private float mLastMotionX; // private int mTouchSlop; private int currentScreenIndex=0; private boolean autoScroll=true; private int scrollTime=3*1000; private int currentWhat=0; private Handler handler=new Handler() { @Override public void handleMessage(Message msg) { if(autoScroll && currentWhat==msg.what) { currentScreenIndex=(currentScreenIndex+1)%getChildCount(); scrollToScreen(currentScreenIndex); Log.i("TAG","handleMessage scrollToScreen:"+currentScreenIndex); if(autoScroll) handler.sendEmptyMessageDelayed(currentWhat, scrollTime); } } }; public BannerLayout(Context context) { super(context); initView(context); // TODO Auto-generated constructor stub } public BannerLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub initView(context); } public BannerLayout(Context context, AttributeSet attrs) { super(context, attrs); initView(context); // TODO Auto-generated constructor stub } private void initView(final Context context) { this.scroller = new Scroller(context, new DecelerateInterpolator(2));//OvershootInterpolator(1.1f) handler.sendEmptyMessageDelayed(currentWhat, scrollTime); // final ViewConfiguration configuration = ViewConfiguration // .get(getContext()); // mTouchSlop = configuration.getScaledTouchSlop(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // super.onMeasure(widthMeasureSpec, heightMeasureSpec); int maxHeight=-1; final int count = getChildCount(); for (int i = 0; i < count; i++) { getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec); maxHeight=Math.max(maxHeight, getChildAt(i).getMeasuredHeight()); } maxHeight=Math.min(maxHeight, MeasureSpec.getSize(heightMeasureSpec)); Log.e("TAG","onMeasure Height:"+maxHeight); setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),maxHeight); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { final int count = getChildCount(); int cLeft = 0; for (int i = 0; i < count; i++) { View child = getChildAt(i); if (child.getVisibility() == View.GONE) continue; // child.setVisibility(View.VISIBLE); final int childWidth = child.getMeasuredWidth(); child.layout(cLeft, 0, cLeft +childWidth, child.getMeasuredHeight()); cLeft += childWidth; } } @Override public void computeScroll() { if (scroller.computeScrollOffset()) { scrollTo(scroller.getCurrX(), 0); postInvalidate(); } } @Override public boolean onTouchEvent(MotionEvent ev) { if (getChildCount() == 0) return false; final int action = ev.getAction(); final float x = ev.getX(); switch (action) { case MotionEvent.ACTION_DOWN: autoScroll=false; currentWhat++; mLastMotionX = x; if (!scroller.isFinished()) { scroller.abortAnimation(); } // Log.i("TAG","ACTION_DOWN"); return true; case MotionEvent.ACTION_MOVE: final int deltaX = (int) (mLastMotionX - x); // boolean xMoved = Math.abs(deltaX) > mTouchSlop; mLastMotionX = x; if((0==currentScreenIndex && deltaX<0) || (getChildCount()-1==currentScreenIndex && deltaX>0)) scrollBy(deltaX/4, 0); else // Log.i("TAG","ACTION_MOVE"); // if (xMoved) scrollBy(deltaX, 0); final int screenWidth = getWidth(); currentScreenIndex=(getScrollX() + (screenWidth / 2))/ screenWidth; return true; case MotionEvent.ACTION_UP: snapToDestination(); if(!autoScroll) { autoScroll=true; handler.sendEmptyMessageDelayed(currentWhat, scrollTime); } break; case MotionEvent.ACTION_CANCEL: snapToDestination(); if(!autoScroll) { autoScroll=true; handler.sendEmptyMessageDelayed(currentWhat, scrollTime); } } return false; } private void scrollToScreen(int whichScreen) { // if (!scroller.isFinished()) // return; // Log.e("TAG","scrollToScreen:"+whichScreen); int delta = 0; delta = whichScreen * getWidth() - getScrollX(); // scroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2); scroller.startScroll(getScrollX(), 0, delta, 0, 1500); invalidate(); currentScreenIndex=whichScreen; } private void snapToDestination() { final int x=getScrollX(); final int screenWidth = getWidth(); scrollToScreen((x + (screenWidth / 2))/ screenWidth); } @Override protected void finalize() throws Throwable { Log.e("TAG","finalize==="); super.finalize(); } }
ok,附图一张
附上源码demo:https://files.cnblogs.com/zhouchanwen/bannerDemo.7z