书到用时方恨少,事非经过不知难。这篇文章主要讲述Android无限广告轮播 - 自定义BannerView相关的知识,希望能为你提供帮助。
1.概述
这其实是我第一篇想写的博客,
可能是因为我遇到了太多的坑,
那个时候刚入行下了很多Demo发现怎么也改不动,
可能是能力有限,
这次就做一个具体的实现和彻底的封装。
上次讲了Android无限广告轮播-ViewPager源码分析,
有了源码分析我们对ViewPager就有了一个大概的了解,
那么再来封装成自定义View,
就会简单许多,
附视频讲解地址:
http://pan.baidu.com/s/1skOdHzn
文章图片
2. 效果封装
2.1 自定义BannerViewPager extends ViewPager:
我们要利用Adapter设计模式, 那么目前这个阶段, 需要的方法就是根据PagerAdapter位置获取当前View, 所以BannerAdapter里面就只需要一个方法那就是getView(int position);
/**
* description:
*广告轮播的ViewPager
* Created by 曾辉 on 2016/11/17.
* QQ:
240336124
* Email: 240336124@
qq.com
* Version:
1.0
*/
public class BannerViewPager extends ViewPager {private Context mContext;
private BannerAdapter mAdapter;
public BannerViewPager(Context context) {
this(context, null);
}public BannerViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext =
context;
}public void setAdapter(BannerAdapter adapter) {
this.mAdapter =
adapter;
setAdapter(new BannerPagerAdapter());
}private class BannerPagerAdapter extends PagerAdapter {@
Override
public int getCount() {
// 返回一个很大的值,
确保可以无限轮播
return Integer.MAX_VALUE;
}@
Override
public boolean isViewFromObject(View view, Object object) {
// 这么写就对了,
看了源码应该就明白
return view =
=
object;
}@
Override
public Object instantiateItem(ViewGroup container, final int position) {
View bannerView =
mAdapter.getView(position);
container.addView(bannerView );
return bannerView;
}@
Override
public void destroyItem(ViewGroup container, int position, Object object) {
// 销毁回调的方法移除页面即可
container.removeView((View) object);
}
}
}
【Android无限广告轮播 - 自定义BannerView】这样我们只要给他设置一个BannerAdapter就可以实现ViewPager的效果, 可以手动切换, 这里就先不看效果。
2.2. 实现自动轮播
实现自动轮播比较简单, 实现的方式有多种可以用定时器Timer、Handler发送消息、start Thread的行, 这里我采用Handler发送消息的方法。
// 2.实现自动轮播 - 发送消息的msgWhat
private final int SCROLL_MSG =
0x0011;
// 2.实现自动轮播 - 页面切换间隔时间
private int mCutDownTime =
3500;
// 2.实现自动轮播 - 发送消息Handler
private Handler mHandler =
new Handler(){
@
Override
public void handleMessage(Message msg) {
// 每隔*s后切换到下一页
setCurrentItem(getCurrentItem() +
1);
// 不断循环执行
startRoll();
}
};
/**
* 2.实现自动轮播
*/
public void startRoll(){
// 清除消息
mHandler.removeMessages(SCROLL_MSG);
// 消息延迟时间让用户自定义有一个默认3500
mHandler.sendEmptyMessageDelayed(SCROLL_MSG,mCutDownTime);
Log.e(TAG,"
startRoll"
);
}/**
* 2.销毁Handler停止发送解决内存泄漏
*/
@
Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mHandler.removeMessages(SCROLL_MSG);
mHandler =
null;
}
我们看一下效果吧, 但是发现Gif录制根本捕捉不到切换的效果, 因为自动切换速度太快了, 这里还是不贴效果了, 下面我就需要改变切换的速度。
2.3. 改变切换速率
如果看过上篇文章的源码就知道, 我们会调用Scroller的mScroller.startScroll(sx, sy, dx, dy, duration)的这个方法, 如果我们需要改变速率就只能改变duration执行切换页面动画的时间, 可是我们根本拿不到这个值, 那么就只能修改mScroller这个属性, 可又发现他是private的有点头大, 但是我们可以利用反射设置mScroller;
// 3.改变ViewPager切换的速率 - 自定义的页面切换的Scroller
private BannerScroller mScroller;
public BannerViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
try {
// 3.改变ViewPager切换的速率
// 3.1 duration 持续的时间局部变量
// 3.2.改变 mScroller private 通过反射设置
Field field =
ViewPager.class.getDeclaredField("
mScroller"
);
// 设置参数第一个object当前属性在哪个类第二个参数代表要设置的值
mScroller =
new BannerScroller(context);
// 设置为强制改变private
field.setAccessible(true);
field.set(this,mScroller);
} catch (Exception e) {
e.printStackTrace();
}
}/**
* 3.设置切换页面动画持续的时间
*/
public void setScrollerDuration(int scrollerDuration){
mScroller.setScrollerDuration(scrollerDuration);
}
现在效果差不多了, 可以看到能够无限轮播, 能够自动轮播, 并且页面切换的速度也可以了, 接下来就只需要处理点的指示器和文字的描述:
文章图片
2.4. 自定义BannerView加入点指示和广告描述
接下来我们又自定义一个BannerView里面包含当前自定义好的BannerViewPager和点的指示LinearLayout以及广告描述TextView。
package com.example.hui.androidtemplate.banner;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.example.hui.androidtemplate.R;
/**
* description:
* <
p/>
* Created by 曾辉 on 2016/11/18.
* QQ:
240336124
* Email: 240336124@
qq.com
* Version:
1.0
*/
public class BannerView extends RelativeLayout{
// 4.自定义BannerView - 轮播的ViewPager
private BannerViewPager mBannerVp;
// 4.自定义BannerView - 轮播的描述
private TextView mBannerDescTv;
// 4.自定义BannerView - 点的容器
private LinearLayout mDotContainerView;
// 4.自定义BannerView - 自定义的BannerAdapter
private BannerAdapter mAdapter;
public BannerView(Context context) {
this(context, null);
}public BannerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}public BannerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 把布局加载到这个View里面
inflate(context, R.layout.ui_banner_layout,this);
initView();
}/**
* 初始化View
*/
private void initView() {
mBannerVp =
(BannerViewPager) findViewById(R.id.banner_vp);
mBannerDescTv =
(TextView) findViewById(R.id.banner_desc_tv);
mDotContainerView =
(LinearLayout) findViewById(R.id.dot_container);
}/**
* 4.设置适配器
*/
public void setAdapter(BannerAdapter adapter){
mBannerVp.setAdapter(adapter);
}/**
* 4.开始滚动
*/
public void startRoll() {
mBannerVp.startRoll();
}
}
2.5. 初始化点的指示器
/**
* 5.初始化点的指示器
*/
private void initDotIndicator() {
// 获取广告的数量
int count =
mAdapter.getCount();
// 让点的位置在右边
mDotContainerView.setGravity(Gravity.RIGHT);
for (int i =
0;
i<
count;
i+
+
){
// 不断的往点的指示器添加圆点
DotIndicatorView indicatorView =
new DotIndicatorView(mContext);
// 设置大小
LinearLayout.LayoutParams params =
new
LinearLayout.LayoutParams(dip2px(8),dip2px(8));
// 设置左右间距
params.leftMargin =
params.rightMargin =
dip2px(2);
indicatorView.setLayoutParams(params);
if(i =
=
0) {
// 选中位置
indicatorView.setDrawable(mIndicatorFocusDrawable);
}else{
// 未选中的
indicatorView.setDrawable(mIndicatorNormalDrawable);
}
mDotContainerView.addView(indicatorView);
}
}/**
* 5.把dip转成px
*/
private int dip2px(int dip) {
return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dip,getResources().getDisplayMetrics());
}
2.6. 阶段性的Bug修复
/**
* 4.设置适配器
*/
public void setAdapter(BannerAdapter adapter){
mAdapter =
adapter;
mBannerVp.setAdapter(adapter);
// 5.初始化点的指示器
initDotIndicator();
// 6.Bug修复
mBannerVp.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener(){
@
Override
public void onPageSelected(int position) {
// 监听当前选中的位置
pageSelect(position);
}
});
// 6.初始化的时候获取第一条的描述
String firstDesc =
mAdapter.getBannerDesc(0);
mBannerDescTv.setText(firstDesc);
}/**
* 6.页面切换的回调
* @
param position
*/
private void pageSelect(int position) {
// 6.1 把之前亮着的点 设置为默认
DotIndicatorView oldIndicatorView =
(DotIndicatorView)
mDotContainerView.getChildAt(mCurrentPosition);
oldIndicatorView.setDrawable(mIndicatorNormalDrawable);
// 6.2 把当前位置的点 点亮position 0 -->
2的31次方
mCurrentPosition =
position%mAdapter.getCount();
DotIndicatorView currentIndicatorView =
(DotIndicatorView)
mDotContainerView.getChildAt(mCurrentPosition);
currentIndicatorView.setDrawable(mIndicatorFocusDrawable);
// 6.3设置广告描述
String bannerDesc =
mAdapter.getBannerDesc(mCurrentPosition);
mBannerDescTv.setText(bannerDesc);
}
2.7. 把指示器的点绘制成圆
/**
* description:圆的指示器
*圆点指示器
* Created by 曾辉 on 2016/11/18.
* QQ:
240336124
* Email: 240336124@
qq.com
* Version:
1.0
*/
public class DotIndicatorView extends View {private Drawable drawable;
public DotIndicatorView(Context context) {
this(context, null);
}public DotIndicatorView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}public DotIndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}@
Override
protected void onDraw(Canvas canvas) {
if(drawable !=
null){
/*drawable.setBounds(0,0,getMeasuredWidth(),getMeasuredHeight());
drawable.draw(canvas);
*/
// 7.把指示器变成圆形
// 画圆
Bitmap bitmap =
drawableToBitmap(drawable);
// 把Bitmap变为圆的
Bitmap circleBitmap =
getCircleBitmap(bitmap);
// 把圆形的Bitmap绘制到画布上
canvas.drawBitmap(circleBitmap,0,0,null);
}
}/**
* 7.获取圆形bitmap
*/
private Bitmap getCircleBitmap(Bitmap bitmap) {
// 创建一个Bitmap
Bitmap circleBitmap =
Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas =
new Canvas(circleBitmap);
Paint paint =
new Paint();
// 设置抗锯齿
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
// 设置仿抖动
paint.setDither(true);
// 在画布上面画个圆
canvas.drawCircle(getMeasuredWidth()/2,getMeasuredHeight()/2,getMeasuredWidth()/2,paint);
// 取圆和Bitmap矩形的交集
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
// 再把原来的Bitmap绘制到新的圆上面
canvas.drawBitmap(bitmap,0,0,paint);
return circleBitmap;
}/**
* 7.从drawable中得到Bitmap
* @
param drawable
* @
return
*/
private Bitmap drawableToBitmap(Drawable drawable) {
// 如果是BitmapDrawable类型
if(drawable instanceof BitmapDrawable){
return((BitmapDrawable)drawable).getBitmap();
}// 其他类型 ColorDrawable
// 创建一个什么也没有的bitmap
Bitmap outBitmap =
Bitmap.createBitmap(getMeasuredWidth(),getMeasuredHeight(), Bitmap.Config.ARGB_8888);
// 创建一个画布
Canvas canvas =
new Canvas(outBitmap);
// 把drawable化到Bitmap上
drawable.setBounds(0,0,getMeasuredWidth(),getMeasuredHeight());
drawable.draw(canvas);
return outBitmap;
}/**
* 5.设置Drawable
*/
public void setDrawable(Drawable drawable) {
this.drawable =
drawable;
// 重新绘制View
invalidate();
}
}
2.8. 设置自定义属性
/**
* 8.初始化自定义属性
*/
private void initAttribute(AttributeSet attrs) {
TypedArray array =
mContext.obtainStyledAttributes(attrs, R.styleable.BannerView);
// 获取点的位置
mDotGravity =
array.getInt(R.styleable.BannerView_dotGravity, mDotGravity);
// 获取点的颜色(
默认、选中)
mIndicatorFocusDrawable =
array.getDrawable(R.styleable.BannerView_dotIndicatorFocus);
if(mIndicatorFocusDrawable =
=
null){
// 如果在布局文件中没有配置点的颜色有一个默认值
mIndicatorFocusDrawable =
new ColorDrawable(Color.RED);
}
mIndicatorNormalDrawable =
array.getDrawable(R.styleable.BannerView_dotIndicatorNormal);
if(mIndicatorNormalDrawable =
=
null){
// 如果在布局文件中没有配置点的颜色有一个默认值
mIndicatorNormalDrawable =
new ColorDrawable(Color.WHITE);
}
// 获取点的大小和距离
mDotSize =
(int) array.getDimension(R.styleable.BannerView_dotSize,dip2px(mDotSize));
mDotDistance =
(int) array.getDimension(R.styleable.BannerView_dotDistance,dip2px(mDotDistance));
array.recycle();
}
2.9. 自适应高度
// 8.自适应高度 动态指定高度
if(mHeightProportion =
=
0 || mWidthProportion =
=
0){
return;
}
// 动态指定宽高计算高度
int width =
getMeasuredWidth();
// 计算高度
int height =
(int) (width*mHeightProportion/mWidthProportion);
// 指定宽高
getLayoutParams().height =
height;
2.10. 内存优化
写完之后, 可以了就大功告成但是这个时候我们要去优化, 还不好用不? 容易扩展不? 内存优化好没? 在这里就不多写了, 如回收Bitmap, 界面复用, 管理Activity生命周期等等, 一切都在视频里面。
文章图片
如果实在还是看不太懂, 可以看一下我录的频, 可以看一下整个系统架构也可以了解一下整个项目的其他东西: http://pan.baidu.com/s/1skOdHzn。
推荐阅读
- Daydream VR入门基础教程,学习Google VR for Android全景应用示例SimpleVrPanorama制作VR全景应用
- Java如何实现多线程聊天应用程序(S1(服务端编程))
- 如何使用ArrayList打印字符串的所有子序列()
- 飞行时间(ToF)传感器解读和指南
- CSS如何使用var()函数(代码示例)
- 火车站/汽车站所需的最少月台数量|S2(基于Map的方法)
- Arcesium面试经验(全日制FTE校园)
- 模糊集的常见操作及示例和代码
- jQuery如何使用GMaps插件(代码示例指南)