广告轮播图在现在的APP首页比较常见,主要的实现方式有两种,一种是通过ViewPager,一种是通过自定义ViewGroup。前者的实现方式比较简便,本篇文章讲的是第二种方法,有人说用ViewPager不是更方便吗,的确,但是我们通过自己定义ViewGroup,可以更深入了解ViewGroup内部的原理。用别人造的轮子确实方便,但有的时候拆开轮子看看,我们也许会学到更多。
主要的思路如下:
首先,轮播图可以理解为n张图片横向相连,并通过一个单张图片大小的的相框,一次移动一张图片的距离,我们可以通过一个ViewGroup容器来存放这n张图片,然后用Scroller类配合TimeTask和Handler来实现图片的滑动,至于小圆点,也就是一个横向存放圆点图片的LinearLayout布局,可以再继承一个FrameLayout类,把存放图片的ViewGroup和存放圆点的LinearLayout都放进去,并根据图片滑动的位置来设置圆点的切换。
这就需要对ViewGroup的测量过程(onMeasure),布局过程 (onLayout)和绘制过程(onDraw)以及onTouch点击事件的处理有所了解。
直接看代码
ViewGroup类:
package com.imagebanner;
import android.content.Context;
import android.graphics.Canvas;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;
import java.util.Timer;
import java.util.TimerTask;
/**
* 该类是实现图片轮播核心类
*/
public class ImageBannerViewGroup extends ViewGroup {
//子视图个数
private int children ;
//子视图宽度和高度
private int childWidth ;
private int childHeight ;
private int x ;
private int index = 0 ;
private Scroller scroller ;
//图片点击事件的监听器
private ImageBannerListner listner ;
//底部圆点切换的监听器
private ImageBannerViewGroupListener bannerViewGroupListener ;
public ImageBannerViewGroupListener getBannerViewGroupListener() {
return bannerViewGroupListener;
}
public void setBannerViewGroupListener(ImageBannerViewGroupListener bannerViewGroupListener) {
this.bannerViewGroupListener = bannerViewGroupListener;
}
//判断是点击事件还是移动事件的标识
private boolean isClick ;
public ImageBannerListner getListner() {
return listner;
}
public void setListner(ImageBannerListner listner) {
this.listner = listner;
}
public interface ImageBannerListner{
void clickImageIndex( int pos );
}
//判断是否自动轮播的标识
private boolean isAuto = true ;
//自动轮播
private Timer timer = new Timer();
private TimerTask task ;
private Handler autoHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch ( msg.what ){
case 0:
//最后一张的时候返回第一张
if( ++index >= children ){
index = 0 ;
}
scrollTo( childWidth * index , 0 );
//图片切换完毕后通知FrameLayout切换底部圆点
bannerViewGroupListener.selectImage(index);
break;
}
}
};
private void startAuto(){
isAuto = true ;
}
private void stopAuto(){
isAuto = false ;
}
private void init(){
scroller = new Scroller(getContext());
task = new TimerTask() {
@Override
public void run() {
if( isAuto ){
autoHandler.sendEmptyMessage(0);
}
}
};
timer.schedule(task , 100 , 3000 );
}
@Override
public void computeScroll() {
super.computeScroll();
if( scroller.computeScrollOffset()){
scrollTo(index * childWidth, 0);
invalidate();
}
}
public ImageBannerViewGroup(Context context) {
super(context);
init();
}
public ImageBannerViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ImageBannerViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//求出子视图的个数
children = getChildCount();
if( children == 0 ){
setMeasuredDimension(0,0);
}else{
//测量子视图的高度和宽度
measureChildren(widthMeasureSpec,heightMeasureSpec);
//根据子视图的宽度和高度 , 求出该ViewGroup的宽度和高度
View view = getChildAt(0);
childHeight = view.getMeasuredHeight() ;
childWidth = view.getMeasuredWidth() ;
//子视图的总宽度
int width = childWidth * children ;
setMeasuredDimension( width , childHeight );
}
}
/**
*
* @param change 布局位置发生改变时为true
* @param l 相对于父View的Left位置
* @param t 相对于父View的Top位置
* @param r 相对于父View的Right位置
* @param b 相对于父View的Bottom位置
*/
@Override
protected void onLayout(boolean change, int l, int t, int r, int b) {
if( change ){
int leftMargin = 0 ;
for( int i = 0 ; i < children ; i ++ ){
View view = getChildAt(i);
view.layout(leftMargin, 0 , leftMargin + childWidth , childHeight );
leftMargin += childWidth ;
}
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
/**
* 该方法返回true , ViewGroup会处理此次拦截事件
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true ;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch ( event.getAction() ){
case MotionEvent.ACTION_DOWN:
isClick = true ;
stopAuto();
if( !scroller.isFinished() ){
scroller.abortAnimation();
}
x = (int) event.getX();
break ;
case MotionEvent.ACTION_MOVE:
int moveX = (int) event.getX();
int distance = moveX - x ;
scrollBy( -distance , 0 );
x = moveX ;
isClick = false ;
break ;
case MotionEvent.ACTION_UP:
int scrollX = getScrollX() ;
index = ( scrollX + childWidth/2 ) / childWidth ;
if( index < 0 ){
index = 0 ;
}else if( index > children - 1 ){
index = children - 1 ;
}
if( isClick ){
//如果是点击事件
listner.clickImageIndex(index);
}
else{
int dx = index * children - scrollX ;
scroller.startScroll(scrollX , 0 , dx , 0 );
postInvalidate();
bannerViewGroupListener.selectImage(index);
}
startAuto();
/* scrollTo( index * childWidth , 0 );*/
break ;
default:
break ;
}
//返回true的目的是告诉该ViewGroup的父View已经处理该事件
return true ;
}
//
public interface ImageBannerViewGroupListener{
void selectImage( int index ) ;
}
}
FrameLayout类
package com.imagebanner;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Build;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import java.util.List;
/**
* Created by Administrator on 2017/7/9.
*/
public class ImageBannerFrameLayout extends FrameLayout implements ImageBannerViewGroup.ImageBannerViewGroupListener , ImageBannerViewGroup.ImageBannerListner{
private ImageBannerViewGroup imageBannerViewGroup ;
private LinearLayout linearLayout ;
//自定义轮播图的监听器
public FrameLayoutListener listener ;
public FrameLayoutListener getListener() {
return listener;
}
public void setListener(FrameLayoutListener listener) {
this.listener = listener;
}
public ImageBannerFrameLayout(Context context) {
super(context);
initViewGroup();
initDotLinearLayout();
}
public ImageBannerFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initViewGroup();
initDotLinearLayout();
}
public ImageBannerFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initViewGroup();
initDotLinearLayout();
}
//初始化图片轮播功能的核心类
private void initViewGroup(){
imageBannerViewGroup = new ImageBannerViewGroup(getContext());
//设置布局属性
FrameLayout.LayoutParams fp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
imageBannerViewGroup.setLayoutParams(fp);
//为ViewGroup设置底部圆点切换的监听器
imageBannerViewGroup.setBannerViewGroupListener(this);
//为ViewGroup设置图片点击事件的监听器
imageBannerViewGroup.setListner(this);
addView(imageBannerViewGroup);
}
//初始化底部圆点布局
private void initDotLinearLayout(){
linearLayout = new LinearLayout(getContext());
//设置布局属性
FrameLayout.LayoutParams fp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, 40);
linearLayout.setLayoutParams(fp);
linearLayout.setOrientation(LinearLayout.HORIZONTAL);
linearLayout.setGravity(Gravity.CENTER);
linearLayout.setBackgroundColor(Color.GRAY);
addView(linearLayout);
FrameLayout.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);
}
}
//公共方法,向FrameLayout中添加图片
public void addBitmap( List list ){
for( int i = 0 ; i < list.size() ; i ++ ){
Bitmap bitmap = list.get(i);
addBitmapToViewGroup(bitmap);
addDots();
}
}
//向ViewGroup中添加图片
private void addBitmapToViewGroup(Bitmap bitmap){
ImageView iv = new ImageView(getContext());
iv.setScaleType(ImageView.ScaleType.CENTER_CROP);
iv.setLayoutParams( new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
iv.setImageBitmap(bitmap);
imageBannerViewGroup.addView(iv);
}
//向底部linearlayout中添加圆点
private void addDots(){
ImageView iv = new ImageView(getContext());
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
lp.setMargins(15,5,15,5);
iv.setLayoutParams(lp);
iv.setImageResource(R.drawable.dot_normal);
linearLayout.addView(iv);
}
//实现该接口,完成底部圆点的切换
@Override
public void selectImage(int index) {
int count = linearLayout.getChildCount();
for( int i = 0 ; i < count ; i ++ ){
ImageView iv = (ImageView) linearLayout.getChildAt(i);
if( i == index ){
iv.setImageResource(R.drawable.dot_select);
}else {
iv.setImageResource(R.drawable.dot_normal);
}
}
}
@Override
public void clickImageIndex(int pos) {
listener.clickImageIndex(pos);
}
//定义FrameLayout的监听器接口
public interface FrameLayoutListener{
void clickImageIndex( int pos ) ;
}
}
MainActivity代码
package com.imagebanner;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity implements ImageBannerFrameLayout.FrameLayoutListener{
private ImageBannerFrameLayout mGroup ;
private int[] ids = new int[]{
R.drawable.banner1,
R.drawable.banner2,
R.drawable.banner3
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//计算出当前手机的宽度
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
int width = dm.widthPixels;
mGroup = (ImageBannerFrameLayout) findViewById(R.id.image_banner);
mGroup.setListener(this);
List list = new ArrayList<>();
for( int i = 0 ; i < ids.length ; i ++ ){
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),ids[i]);
list.add(bitmap);
}
mGroup.addBitmap(list);
}
//此处填写点击事件相关的业务代码
@Override
public void clickImageIndex(int pos) {
Toast.makeText(this,"点击了第" + pos + "张图片" , Toast.LENGTH_SHORT).show();
}
}
圆点布局文件dot_normal.xml
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@android:color/white">solid>
<size android:height="10dp"
android:width="10dp">size>
shape>
dot_select.xml
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@android:color/holo_red_light">solid>
<size android:height="10dp"
android:width="10dp">size>
shape>
MainActivity布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.imagebanner.MainActivity">
<com.imagebanner.ImageBannerFrameLayout
android:id="@+id/image_banner"
android:layout_width="match_parent"
android:layout_height="200dp">com.imagebanner.ImageBannerFrameLayout>
RelativeLayout>