从慕课网上学了一门叫做“不一样的自定义实现轮播图效果”的课程,感觉实用性较强,而且循序渐进,很适合初学者。在此对该课程做一个小小的笔记。

实现轮播思路:

1、一般轮播图是由一组图片和底部轮播圆点组成,要想组成这种圆点在图片之上的效果,首先我们应当想到FrameLayout布局。最外层应该是一个FrameLayout布局,将轮播图片和圆点添加到这个布局中,并且需要设置圆点的位置在下部正中间(当然视需求而定)。

2、轮播图片组应该是一个ViewGroup,我们需要对该ViewGroup进行测量、布局等过程。

3、圆点集合应该是一个LinearLayout。

4、要想实现自动轮播效果,可以结合Timer、TimerTask以及Handler的配合使用。

5、要想实现手动滑动轮播图,可以利用Scroller。

6、实现底部圆点随图片切换变化的过程,实现每张图片的点击事件。

首先我们来实现图片组ViewGroup的绘制过程。实现思路很简单,对于整个ViewGroup,其高度应该是子View的高度(假设我们的轮播图效果类似于淘宝首页的头部效果),宽度应该是所有子View的宽度之和。抓住这个方向,不难实现测量过程。

#ImaBannerViewGroup 

 @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
child = getChildCount();
if (0 == child) {
setMeasuredDimension(0, 0);
} else {
measureChildren(widthMeasureSpec, heightMeasureSpec);
View view = getChildAt(0);
childHeight = view.getMeasuredHeight();
childWidth = view.getMeasuredWidth(); int height = childHeight;
int width = childWidth * child;
setMeasuredDimension(width, height);
}
}

从上面代码可以看出,我们假定每个子View的宽度都和第一个子View的宽度相等,当后面的图片宽度小于第一张图时就会出现留白现象。一般轮播图每个子View的宽度都是整个屏幕的宽度,我们可以写一个全局的静态变量,然后给该变量赋值为屏幕宽度,在需要的时候进行调用就好。

然后重写onLayout()方法。该方法遍历子视图,设置每个子视图的位置,即在ViewGroup中水平依次平铺。该例子实际上是不用考虑y方向的,x方向后子视图应该是在前一个子视图的位置加上前一个子视图的宽度。

   protected void onLayout(boolean change, int left, int top, int right, int bottom) {
if (change) {//当Viewgroup发生改变时为true
int leftMargin = 0;
for (int i = 0; i < child; i++) {
View view = getChildAt(i);
view.layout(leftMargin, 0, leftMargin + childWidth, childHeight);
leftMargin += childWidth;
}
}
}

接下来就是实现手动轮播了。可以使用ScrollTo,ScrollBy,也可以使用Scroller。既然需要实现手动轮播,自然那涉及到分发过程,也就是需要重写onInterceptTouchEvent()返回true,并重写onTouchEvent()方法。

 /**
* 1)我们在滑动屏幕图片的过程中,其实就是我们自定义ViewGroup的子视图的移动过程,那么,我们只需要知道滑动之前横坐标和滑动之后的横坐标,此时,我们就可以
* 求出此次过程中我们滑动的距离,我们利用scrollBy方法实现图片的滑动,所以,此时我们需要两个值,移动之前和移动之后的横坐标。
* 2)在我们第一次按下的那一瞬,此时的移动之前和移动之后的距离是相等的,也就是我们此时按下那一瞬的那个点的横坐标的值
* 3)不断滑动过程会不断调用ACTION_MOVE,所以需要保存移动 值。
* 4)当我们抬起那一瞬,需要知道具体滑到哪一页
*
* @param event
* @return 返回true的目的是告诉我们该ViewGroup容器的父View我们已经处理好了该事件
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isClick = true;
if (!scroller.isFinished()) {
scroller.abortAnimation();
}
x = (int) event.getX();
stopAuto();
break;
case MotionEvent.ACTION_MOVE:
int moveX = (int) event.getX();
int distance = moveX - x;
if (Math.abs(distance) > filter){
isClick = false;
}
scrollBy(-distance, 0);
x = moveX;
break;
case MotionEvent.ACTION_UP:
startAuto();
int scrollX = getScrollX();
index = (scrollX + childWidth / 2) / childWidth;
if (index < 0) {
index = 0;
} else if (index > child - 1) {
index = child - 1;
}
int dx = index * childWidth - scrollX;
if (isClick) {
listener.clickImageIndex(index);
} else {
/**
* public void startScroll(int startX, int startY, int dx, int dy)
* startX:水平方向滚动的偏移值,以像素为单位,。正值标明向左滚动
* startY:垂直方向滚动的偏移值,正值标明向上滚动
* dx:水平方向滑动的距离,正值向左滚动
* dy:垂直方向滑动的距离,正值向上滚动。
*
* startX 表示起点在水平方向到原点的距离(可以理解为X轴坐标,但与X轴相反),正值表示在原点左边,负值表示在原点右边。
dx 表示滑动的距离,正值向左滑,负值向右滑。
*/
scroller.startScroll(scrollX, 0, dx, 0);
/**
* postInvalidate() 方法在非 UI 线程中调用,通知 UI 线程重绘,(当然也可以在UI线程中调用)。
* invalidate() 方法在 UI 线程中调用,重绘当前 UI。
*/
postInvalidate();
changeLisener.selectImage(index);
}
break;
default:
break;
}
return true;
}

利用Timer、TimerTask、Handler实现自动轮播。

1. 需要两个方法来控制自动轮播的启动和关闭,我们称之为自动轮播的开关,分别为
startAuto(),stopAuto();还需要一个标志来表明当前自动轮播的状态时开启还是关闭,设为布尔类
型isAuto,true表示自动轮播启动,false表示自动轮播关闭。
2. 在ImaBannerViewGroup 的构造方法中,设置一个定时任务,如果自动轮播处于开启状态,则利用

Handler每间隔一段时间发送一个空的消息,而在Handler接受消息后利用scrollTo()方法实现图片的
轮播,相关代码如下:

 private void intiObj() {
scroller = new Scroller(getContext());
task = new TimerTask() {
@Override
public void run() {
if (isAuto) {
autoHander.sendEmptyMessage(0);
}
}
};
timer.schedule(task, 100, 3000);
} private Handler autoHander = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
if (++index >= child) {
index = 0;
}
scrollTo(childWidth * index, 0);
changeLisener.selectImage(index);
break;
default:
break;
}
}
};

在点击发生时,关闭自动轮播,抬起手后需要开启自动轮播,实现过程只需要在onTouchEvent()方法中,

当MotionEvent.ACTION_DOWN时,调用stopAuto();当MotionEvent.ACTION_UP时,调用
startAuto()即可。同时,利用一个布尔型变量isClick判断点击事件,当用户离开屏幕的一瞬间,来判断是点击事件还是移动事件。当按下即触发ACTION_DOWN时,设置为true,ACTION_MOVE时,若移动距离大于最小移动距离,则设置为false,当ACTION_UP的时候,根据isClick值判断是移动还是点击,进行相应的操作即可。

最后实现底部轮播圆点的布局以及切换的过程,首先我们需要自定义一个FrameLayout布局,代码如下:

public class ImageBannerFrameLayout extends FrameLayout implements DotChangeLisener, ImageBannerListener {
private ImaBannerViewGroup imaBannerViewGroup;
private LinearLayout linearLayout;
private FrameLayoutListenenr layoutListenenr; public FrameLayoutListenenr getLayoutListenenr() {
return layoutListenenr;
} public void setLayoutListenenr(FrameLayoutListenenr layoutListenenr) {
this.layoutListenenr = layoutListenenr;
} public ImageBannerFrameLayout(Context context) {
super(context);
initBannerViewGroup();
initDotLayout();
} public ImageBannerFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initBannerViewGroup();
initDotLayout();
} public ImageBannerFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initBannerViewGroup();
initDotLayout();
}
private void initBannerViewGroup() {
imaBannerViewGroup = new ImaBannerViewGroup(getContext());
LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
imaBannerViewGroup.setLayoutParams(lp);
imaBannerViewGroup.setChangeLisener(this);
imaBannerViewGroup.setListener(this);
addView(imaBannerViewGroup);
}
private void initDotLayout() {
linearLayout = new LinearLayout(getContext());
LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 40);
linearLayout.setLayoutParams(lp);
linearLayout.setOrientation(LinearLayout.HORIZONTAL);
linearLayout.setGravity(Gravity.CENTER);
linearLayout.setBackgroundColor(Color.RED);
addView(linearLayout);
LayoutParams layoutParams = (LayoutParams) linearLayout.getLayoutParams();
layoutParams.gravity = Gravity.BOTTOM;
linearLayout.setLayoutParams(layoutParams);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
linearLayout.setAlpha(0.5f);
} else {
linearLayout.getBackground().setAlpha(100);
}
}
public void addBitmap(List<Bitmap> list) {
for (int i = 0; i < list.size(); i++) {
Bitmap bitmap = list.get(i);
addBitmapToBannerViewGroup(bitmap);
addDotToLayout();
}
}
private void addDotToLayout() {
ImageView imageView = new ImageView(getContext());
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
lp.setMargins(5, 5, 5, 5);
imageView.setLayoutParams(lp);
imageView.setImageResource(R.drawable.doc_normal);
linearLayout.addView(imageView);
}
private void addBitmapToBannerViewGroup(Bitmap bitmap) {
ImageView imageView = new ImageView(getContext());
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
imageView.setLayoutParams(new ViewGroup.LayoutParams(Constant.WIDTH, ViewGroup.LayoutParams.WRAP_CONTENT));
imageView.setImageBitmap(bitmap);
imaBannerViewGroup.addView(imageView);
}
@Override
public void selectImage(int index) {
int count = linearLayout.getChildCount();
for (int i = 0; i < count; i++) {
ImageView imageView = (ImageView) linearLayout.getChildAt(i);
if (i == index) {
imageView.setImageResource(R.drawable.doc_normal);
} else {
imageView.setImageResource(R.drawable.doc_unnormal);
}
}
}
@Override
public void clickImageIndex(int pos) {
layoutListenenr.clickImageByIndex(pos);
}
}

关于自定的FrameLayout布局,我们需要先加载图片的ViewGroup,然后加载点集的LinearLayout;需要对图片进行监听,添加接口进行相应的处理;提供addBitmap方法,以便调用者设置轮播图片,并每张图片和每个圆点一一映射。

MainActivity中的调用方法如下:

public class MainActivity extends AppCompatActivity implements ImageBannerListener, FrameLayoutListenenr, DotChangeLisener {

    private ImageBannerViewGroup group;
private ImaBannerViewGroup viewGroup;
private ImageBannerFrameLayout frameLayout; private int[] ids = new int[]{R.mipmap.top, R.mipmap.second, R.mipmap.qq};
private int[] idss = new int[]{R.mipmap.top, R.mipmap.second, R.mipmap.qq};
private int[] idds = new int[]{R.mipmap.top, R.mipmap.second, R.mipmap.qq};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
Constant.WIDTH = dm.widthPixels; group = findViewById(R.id.group);
viewGroup = findViewById(R.id.scrollViewGroup);
frameLayout = findViewById(R.id.contentGroup);
List<Bitmap> list = new ArrayList<>();
for (int i = 0; i < idds.length; i++){
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), idds[i]);
list.add(bitmap);
} frameLayout.addBitmap(list);
for (int i = 0; i < ids.length; i++) {
ImageView imageView = new ImageView(this);
/**
* 解决空白的bug
*/
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
imageView.setLayoutParams(new ViewGroup.LayoutParams(Constant.WIDTH, ViewGroup.LayoutParams.WRAP_CONTENT));
imageView.setImageResource(ids[i]);
group.addView(imageView);
}
for (int i = 0; i < idss.length; i++) {
ImageView imageView = new ImageView(this);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setLayoutParams(new ViewGroup.LayoutParams(Constant.WIDTH, ViewGroup.LayoutParams.WRAP_CONTENT));
imageView.setImageResource(idss[i]);
viewGroup.addView(imageView);
}
viewGroup.setListener(this);
viewGroup.setChangeLisener(this);
frameLayout.setLayoutListenenr(this);
}
@Override
public void clickImageIndex(int pos) {
Toast.makeText(this, "pos = " + pos, Toast.LENGTH_LONG).show();
} @Override
public void clickImageByIndex(int pos) {
Toast.makeText(this, "pos = " + pos, Toast.LENGTH_LONG).show();
} @Override
public void selectImage(int index) {
}
}

因为我实现的是个循序渐进的过程,所有有多余代码,大家可以仅参考FrameLayout部分。

代码补充:

//分页符的监听
public interface DotChangeLisener {
void selectImage(int index);
} //FrameLayout上面点击每张图片的监听
public interface FrameLayoutListenenr {
void clickImageByIndex(int pos);
} //点击每张图片的监听
public interface ImageBannerListener {
void clickImageIndex(int pos);
} //定义一个全局变量,即每张图片的宽度
public class Constant {
public static int WIDTH = 0;
}

上述代码基本就可以实现轮播效果了。

Android轮播图Banner的实现的更多相关文章

  1. Android 轮播图Banner切换图片的效果

    Android XBanner使用详解 2018年03月14日 08:19:59 AND_Devil 阅读数:910   版权声明:本文为博主原创文章,未经博主允许不得转载. https://www. ...

  2. android 使用图片轮播图---banner 使用

    转自:https://github.com/youth5201314/banner 使用步骤 Step 1.依赖banner Gradle dependencies{ compile 'com.you ...

  3. 029 Android 轮播图广告Banner开源框架使用

    1.Banner介绍 现在的绝大数app都有banner界面,实现循环播放多个广告图片和手动滑动循环等功能. 2.使用环境配置(具体可见github开源项目) (1)添加依赖 在build.gradl ...

  4. android 轮播图

    轮播图是很常用的一个效果 核心功能已经实现 没有什么特殊需求 自己没事研究的 所以封装的不太好 一些地方还比较糙 为想要研究轮播图的同学提供个参考 目前测试图片为mipmap中的图片 没有写从网络加载 ...

  5. Android轮播图

    轮播图是很常用的一个效果 核心功能已经实现 没有什么特殊需求 自己没事研究的 所以封装的不太好 一些地方还比较糙 为想要研究轮播图的同学提供个参考目前测试图片为mipmap中的图片 没有写从网络加载图 ...

  6. android轮播图的实现原理

    1.轮播图的点:RadioGroup,根据网络请求的数据,解析得到的图片的个数,设置RadioGroup的RadioButton的个数. 2.轮播图的核心技术:用Gallery来存放图片,设置适配器. ...

  7. [android] 轮播图-滑动图片标题焦点

    谷歌提供的v4包,ViewPager 在布局文件中,先添加<android.support.v4.view.ViewPager/>控件,这个只是轮播的区域 在布局文件中,布置标题描述部分 ...

  8. [android] 轮播图-无限循环

    实现无限循环 在getCount()方法中,返回一个很大的值,Integer.MAX_VALUE 在instantiateItem()方法中,获取当前View的索引时,进行取于操作,传递进来的int ...

  9. 自定义完美的ViewPager 真正无限循环的轮播图

    网上80%的思路关于Android轮播图无限循环都是不正确的,不是真正意义上的无限循环, 其思路大多是将ViewPager的getCount方法返回值设置为Integer.MAX_VALUE, 然后呢 ...

随机推荐

  1. 20145315《网络对抗》——注入shellcode以及 Return-to-libc攻击实验

    shellcode 准备一段Shellcode 我用的老师的shellcode:\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3 ...

  2. Python3基础 else 循环完整结束才执行

             Python : 3.7.0          OS : Ubuntu 18.04.1 LTS         IDE : PyCharm 2018.2.4       Conda ...

  3. linux下查找指定后缀的文件

    1.linux下查找指定后缀的文件 例如查找当前目录下的所有后缀名时.c或.h的文件 find  .  -type f -regex  ".*\.\(c\|h\)"

  4. aws相关文档

    使用 IAM 角色授予对 Amazon EC2 上的 AWS 资源的访问权 https://docs.aws.amazon.com/zh_cn/sdk-for-java/v1/developer-gu ...

  5. 桌面共享UDP组播实现

    组播(Multicast)传输:在发送者和每一接收者之间实现点对多点网络连接.如果一台发送者同时给多个的接收者传输相同的数据,也只需复制一份的相同数据包.它提高了数据传送效率.减少了骨干网络出现拥塞的 ...

  6. js中一些关于比较左右两边的值的题目

    alert(typeof(NaN)); alert(typeof(Infinity)); alert(typeof(null)); alert(typeof(undefined)); alert(Na ...

  7. UVa 10118 免费糖果(记忆化搜索+哈希)

    https://vjudge.net/problem/UVA-10118 题意: 桌上有4堆糖果,每堆有N颗.佳佳有一个最多可以装5颗糖的小篮子.他每次选择一堆糖果,把最顶上的一颗拿到篮子里.如果篮子 ...

  8. Android广播接收器里弹出对话框

    不多说,直接上车... public class MyReceiver extends BroadcastReceiver { @Override public void onReceive(fina ...

  9. python ros 关闭节点

    def myhook(): print "shutdown time!" rospy.on_shutdown(myhook) 或 rospy.signal_shutdown(rea ...

  10. Ubuntu 14.04 编写service 服务

    有时我们需要将特定操作封装成服务,通过服务启动停止,例如nginx的启动停止,service nginx start 或者service nginx stop 下面我们将编写一个demo cd /et ...