使用HorizontalScrollView实现广告栏Banner及相关原理分析_第1页
使用HorizontalScrollView实现广告栏Banner及相关原理分析_第2页
使用HorizontalScrollView实现广告栏Banner及相关原理分析_第3页
使用HorizontalScrollView实现广告栏Banner及相关原理分析_第4页
使用HorizontalScrollView实现广告栏Banner及相关原理分析_第5页
已阅读5页,还剩3页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、使用HorizontalScrollView实现广告栏Banner及相关原理分析 现在的App中,广告栏Banner的使用还是挺广泛的,用于展示各种广告、活动推荐等。使用HorizontalScrollView可以很简单的实现一个可自动播放、可滑动、可点击的广告栏Banner,这个也可以做为一个例子,来学习自定义控件的制作。相关原理主要包括两个方面:onMeasure、onLayout、onDraw等View、ViewGroup相关布局函数;dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent等触摸事件处理函数(包括Click等事件触发);

2、 这两部分搞懂后,制作自定义控件就得心应手了。一、需求控件每次展示一张图片,隔一段时间换播下一张,如果当前是最后一张则展示第一张;用户手指触摸控件,停止轮播,用户滑动手指,则根据方向展示相应下一张或前一张图片;如果是第一张图片继续往前滑动,需要展示最后一张图片,如果最后一张图片往后滑动,展示第一张图片;最下方需要有小白点来指示当前是第几张图片;最终效果如图: 这里写图片描述二、初设计 HorizontalScrollView本来就是一个横向滑动组件,使用它可以很方便的实现滑动及相应的动画效果,所以选择用它来写这个控件,我看网上也有使用ViewPager实现,原理都是大同小异;下面是按上面的需求

3、做的初始设计,在实现的过程中还会碰到其他问题,需要按情况解决。布局 HorizontalScrollViewLinearLayoutImageView List 同时需要在HorizontalScrollView上画小白点指示当前页定时滚动 添加一个定时器,每隔一段时间滑动到下一页,注意最后一页的循环处理。用户事件 添加事件监控,触摸停止定时器及滑动事件处理三、具体实现1. 设计布局 xml中的布局只有最外层控件,其他的LinearLayout和ImageView都是动态添加进去的,实现如下:public class ADPager extends HorizontalScrollView p

4、rivate LinearLayout container = null; private LinearLayout.LayoutParams imgLayoutParams = null; public ADPager(Context context) super(context); init(); private void init() Context ctx = getContext(); container = new LinearLayout(ctx); ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(

5、ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); container.setLayoutParams(layoutParams); /横向布局 container.setOrientation(LinearLayout.HORIZONTAL); imgLayoutParams = new LinearLayout.LayoutParams(getWidth(),getHeight(); this.addView(container); this.setSmoothScrollingEnabled

6、(true); /不显示滑动条 this.setHorizontalScrollBarEnabled(false); public void setImageList(int imgArray) int size = imgArray.length; if(size > 1) /如果大于一张图片,第一张前放最后一张图片 this.container.addView(makeImageView(imgArraysize - 1); for(int imgId:imgArray) this.container.addView(makeImageView(imgId); if(size >

7、; 1) /如果大于一张图片,最后一张后放第一张图片 this.container.addView(makeImageView(imgArray0); public ImageView makeImageView(int resourceId) ImageView imageView; Context ctx = getContext(); imageView = new ImageView(ctx); imageView.setImageResource(resourceId); imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);

8、 imageView.setLayoutParams(imgLayoutParams); return imageView;在onCreate中调用初始化图片: int imgIdArray = R.drawable.img1,R.drawable.img2,R.drawable.img3; ADPager adPager = (ADPager)findViewById(R.id.adpager); adPager.setImageList(imgIdArray); 然后运行就碰到了第一个坑,根本没有图片被展示出来,原因是:在初始化时,我们尝试使用getWidth和getHeight函数来获取

9、宽度和高度,然后设置图片大小,但在View还没有展示出来时,其实通过这两个函数是不能获取宽高的,比如在onCreate/onStart/onResume中,详见: Activity中获取view的高度和宽度为0的原因以及解决方案 在上面的文章中,也提到了几种获取的方式,但我们是自定义控件,还有他方式来获取: Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) super.onMeasure(widthMeasureSpec, heightMeasureSpec); imgLayoutPar

10、ams.width = getMeasuredWidth(); imgLayoutParams.height = getMeasuredHeight(); 这种方法使用了onMeasure函数,现在只要知道这个函数是用来测量自己及子View的大小就可以了,后面还会系统总结。现在已经可以展示出图片,且可以自由滑动,当然,现在还简陋的很: 这里写图片描述 只不过有多张图片时,我们显示的是最后一张图片,是因为我们为了第一张图片还可以往前滑动,在前面添加的,所以我们需要在初始时,滑动到第一张图片展示: public void scrollToPage(int page,boolean isSmooth

11、) if(page < 0) page = mTotalSize - 1; else if(page >= mTotalSize) page = 0; /设置当前页 mCurrPage = page; int width = getWidth(); /因为第一张前面加了一张,所以页数需要+1。而只有一张图片时,scrollTo其实没有产生效果 if(isSmooth) this.smoothScrollTo(page + 1) * width, 0); else this.scrollTo(page + 1) * width, 0); Override protected void

12、 onLayout(boolean changed, int l, int t, int r, int b) super.onLayout(changed, l, t, r, b); scrollToPage(0,false); 2. 添加小白点 小白点用来指示当前是哪张图片,位于中间下方,且不随图片滑动而移动。所以只能画在最上层的HorizontalScrollView上。这里我们在onDraw画控件函数中,直接在画版上画: private void initPaint() mStrokePaint = new Paint(); /抗锯齿 mStrokePaint.setAntiAlias(

13、true); /空心线宽 mStrokePaint.setStrokeWidth(1.0f); /中空 mStrokePaint.setStyle(Paint.Style.STROKE); /颜色 mStrokePaint.setColor(Color.WHITE); mFillPaint = new Paint(); mFillPaint.setAntiAlias(true); mFillPaint.setStyle(Paint.Style.FILL_AND_STROKE); mFillPaint.setColor(Color.WHITE); Override protected void

14、dispatchDraw(Canvas canvas) super.dispatchDraw(canvas); int width = getWidth(); float density = getContext().getResources().getDisplayMetrics().density; /半径转换为像素 int radiusInPixel = (int)(CIRCLE_RADIUS * density); /白点间隔 int margin = radiusInPixel; /白点区域总宽度 int totalWidth = radiusInPixel * 2 * mTotal

15、Size + margin * (mTotalSize - 1); /初始第一个点位置 int offsetX = getScrollX() + width / 2 - totalWidth / 2 + radiusInPixel; int offsetY = (int)(getHeight() - density * 10 - radiusInPixel); /开始画点 for(int i = 0;i < mTotalSize; i+) if(i = mCurrPage) canvas.drawCircle(offsetX,offsetY,radiusInPixel,mFillPain

16、t); else canvas.drawCircle(offsetX,offsetY,radiusInPixel,mStrokePaint); offsetX += radiusInPixel * 2 + margin; 可以看到我代码中其实是使用dispatchDraw来画的,而不是上面说的onDraw函数。是因为我在实现时又踩了一个坑,因为View会先调用onDraw来画自己的东西,然后调用dispatchDraw去画孩子(当然,View没有孩子,这个只有在ViewGroup中才有用)/ Step 3, draw the contentif (!dirtyOpaque) onDraw(ca

17、nvas);/ Step 4, draw the childrendispatchDraw(canvas); 如果在onDraw中去画点,则会被后来画的孩子遮挡住,这个可以将Container不添加到父节点中去来测试,可以看到我们画的圆。所以应该在dispatchDraw中,画完孩子然后去画点。 当然,在网上也看到如果没有背景会跳过onDraw直接调用dispatchDraw的说法,实验结果并不是这样。3. 自动滑动 这个比较简单,不过其中也碰到了一个坑,内存泄漏问题。这个可以看下前面的一个杂记:Android内存泄露杂记2016-02-26 具体就是匿名Runnable引用外部数据,后来使用

18、WeakReference解决,代码如下: public static class AutoPlayRunable implements Runnable private WeakReference<ADPager> reference = null; public AutoPlayR(ADPager adPager) reference = new WeakReference<ADPager>(adPager); Override public void run() ADPager adPager = reference.get(); if(adPager != nu

19、ll) int page = adPager.getCurrPage(); adPager.scrollToPage(page + 1,true); adPager.postDelayed(adPager.getAutoPlayRunnable(),AUTO_PLAY_DUATION); 现在广告就可以自动滚动起来了: 这里写图片描述4.触摸事件 但是现在还无法通过触摸来顺畅控制广告移动,就和上图一样。因为HorizontalScrollView自己处理了触摸事件,通过手指来自由滑动。但这不是我们想要的结果,我们需要的是通过触摸,可以左右滑动,但超过一半,就应该显示下一张,或者没超过一半退回,

20、而不是停在中间。然后就是手指滑动的够快,就算不超过一半也需要到下一张,就和有惯性一样。想要实现这样的结果,我们需要重写触摸事件处理,幸运的是,Android是支持这样做的。 这需要使用到几个触摸事件接口,并对其流程足够了解。一共涉及3个接口,如下: View里,有两个回调函数 :1. public boolean dispatchTouchEvent(MotionEvent ev); 2. public boolean TouchEvent(MotionEvent ev); ViewGroup里,有三个回调函数 :1. public boolean dispatchTouchEvent(Mot

21、ionEvent ev); 2. public boolean onInterceptTouchEvent(MotionEvent ev); 3. public boolean onTouchEvent(MotionEvent ev); 在Activity里,有两个回调函数 :1. public boolean dispatchTouchEvent(MotionEvent ev); 2. public boolean onTouchEvent(MotionEvent ev); 事件传递默认是从父节点开始,直到传递到View。也就是说传递过程是Activity-ViewGroup-View。 触

22、摸事件是由一系列的ACTION_DOWN、ACTION_MOVEMOVEMOVE、ACTION_UP的过程 对上面的接口来说,事件包含三个处理方式,一是分发(dispatchTouchEvent),二是拦截(onInterceptTouchEvent),一个是消费(onTouchEvent),并都有其返回值。分发: dispatchTouchEvent返回true则顺序下发会中断(一般表示事件被消费),后续节点接收不到事件(分发是深度优先的),返回false继续分发拦截: onInterceptTouchEvent比较复杂,return true可以拦截DOWN、MOVE、UP事件 拦截DOW

23、N事件,则表示事件完全由当前ViewGroup来处理,后续MOVE、UP事件也会来找当前ViewGroup拦截MOVE、UP事件,则表示后续事件由当前ViewGroup来处理,之前处理事件的View会收到一个ACTION_CANCEL事件消费: onTouchEvent事件,如果前面没有被拦截: View(如果可点击)默认返回true,表示消费事件消费Down事件,则后续MOVE、UP事件都会来找当前View没消费Down事件,则其他事件也没有你什么事了,不会传递给你的许多事件依赖于onTouchEvent处理UP事件,如Click事件配上伪代码:View mTarget=null;/保存捕获

24、Touch事件处理的Viewpublic boolean dispatchTouchEvent(MotionEvent ev) /.其他处理,在此不管 if(ev.getAction()=KeyEvent.ACTION_DOWN) /每次Down事件,都置为Null if(!onInterceptTouchEvent() mTarget=null; View views=getChildView(); for(int i=0;i<views.length;i+) if(viewsi.dispatchTouchEvent(ev) mTarget=viewsi; return true; /

25、当子View没有捕获down事件时,ViewGroup自身处理。这里处理的Touch事件包含Down、Up和Move if(mTarget=null) return super.dispatchTouchEvent(ev); /.其他处理,在此不管 if(onInterceptTouchEvent() /.其他处理,在此不管 /这一步在Action_Down中是不会执行到的,只有Move和UP才会执行到。 return mTarget.dispatchTouchEvent(ev);更具体的知识储备: Android中的dispatchTouchEvent()、onInterceptTouchE

26、vent()和onTouchEvent() Android:30分钟弄明白Touch事件分发机制 现在我们就可以来想上面的问题了:很明显,ScrollView通过处理Move事件,来进行子View的滑动及动画,所以不能动这个事件;然后还需要处理UP事件来确定手指抬起后,展示哪张图片;最后,还需要得到用户手指滑动的速度,速度快则直接展示下一页;还有别忘了,手指落下时移除自动播放,抬起时开始自动播放。(考虑一下,为什么移除自动播放不能放在onTouchEvent事件中去处理?)最后代码如下: Override public boolean onInterceptTouchEvent(MotionEvent ev) switch (ev.getAction() case MotionEvent.ACTION_DOWN: /停止自动播放 removeCallbacks(mAutoPlayRunnable); break; return super.onInterceptTouchEvent(ev); Override public boolean onTouchEvent(MotionEvent ev) if(mVelocityTracker = null) mVelo

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论