作为一名Android开发者对于自定义View的掌握体现利润他对控件的深入理解,我下面是自定义ViewGroup实现轮播图;
效果图
效果图就是这样,自动轮播,可对每张图片进行点击监听,接下来我们先看一下自定义ViewGroup实现实现的代码:
public class ImageBannerViewGroup extends ViewGroup {
private int lastX; //滑动的上一次位置
private int childerCount; //子控件的个数
private int width; //屏幕的宽度
private int height; //Banner图的高度
private int index = 0; //Banner图索引
private int startX; //滑动的起始位置
private Timer timer;
private List<Integer> banners;
private Boolean isClick = true; //是否点击
private Boolean isAuto = true; //是否自动轮播
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 0) {
if (++index >= childerCount) {
index = 0;
}
if (bannerDotListener != null) {
bannerDotListener.bannerDotListener(index);
}
scrollTo(index * width, 0);
}
}
};
public void setAuto(Boolean isAuto) {
this.isAuto = isAuto;
}
public ImageBannerViewGroup(Context context) {
this(context, null);
}
public ImageBannerViewGroup(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ImageBannerViewGroup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
if (isAuto) {
mHandler.sendEmptyMessage(0);
}
}
}, 1000, 3000);
}
//测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
childerCount = getChildCount();
if (childerCount == 0) {
setMeasuredDimension(0, 0);
} else {
measureChild(getChildAt(0), widthMeasureSpec, heightMeasureSpec);
DisplayMetrics dm = new DisplayMetrics();
((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(dm);
width = dm.widthPixels;
height = dm.heightPixels / 3;//轮播图为手机全屏高度的三分之一
setMeasuredDimension(width * childerCount, height);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed) {
int leftMargin = 0;
for (int i = 0; i < childerCount; i++) {
View view = getChildAt(i);
view.layout(leftMargin, 0, leftMargin + width, height);
leftMargin += width;
}
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isClick = true;
isAuto = false;
startX = (int) event.getX();
break;
case MotionEvent.ACTION_MOVE:
isClick = false;
lastX = (int) event.getX();
int offsetX = startX - lastX;
if (index == 0 && offsetX < 0) {
return true;
} else if (index == childerCount - 1 && offsetX > 0) {
return true;
}
scrollBy(offsetX, 0);
startX = lastX;
break;
case MotionEvent.ACTION_UP:
index = (getScrollX() + width / 2) / width;
Log.e("点击", "onTouchEvent:click " + isClick);
if (isClick && bannerImagClickListener != null) {
bannerImagClickListener.bannerClick(index);
Log.e("点击", "onTouchEvent:click " + index);
} else {
if (index < 0) {
index = 0;
} else if (index == childerCount - 1) {
index = childerCount - 1;
}
if (bannerDotListener != null) {
bannerDotListener.bannerDotListener(index);
}
scrollTo(index * width, 0);
isAuto = true;
}
break;
}
return true;
}
/**
* banner图点击事件回调接口
*/
public interface BannerImagClickListener {
void bannerClick(int pos);
}
private BannerImagClickListener bannerImagClickListener;
public void setBannerImagClickListener(BannerImagClickListener bannerImagClickListener) {
this.bannerImagClickListener = bannerImagClickListener;
}
/**
* banner图的dot点的滑动监听
*/
public interface BannerDotListener {
void bannerDotListener(int pos);
}
private BannerDotListener bannerDotListener;
public void setBannerDotListener(BannerDotListener bannerDotListener) {
this.bannerDotListener = bannerDotListener;
}
}
虽然代码不多,但是逻辑要理清,对于自定义ViewGroup,分三步走:
- onMeasure()测量
- onLayout()布局
- onDraw()绘制
这里我们只用了前两个对父布局和子布局进行了测量和,布置,具体看上面的代码,打了很多注释,多看应该很清楚,这里有个知识点就是ScrollerTo和ScrollerBy的使用规则,其实就是绝对滑动和相对滑动,在ViewGroup中滑动的是子布局,在其他子View中,比如TextView,实现滑动的话,将会是内部内容也就是TextView中的字体进行滑动,还有就是在OnTouchEvent中对触摸事件的判断逻辑,以及在ViewGroup中如何进行事件传递,就是责任链模式,这里我大概说一下ViewGroup中事件的传递,首先在Activity中接收到触摸事件,在ViewGroup中首先对调用自身的onInterceptTouchEvent事件,根据返回的Boolean值判断该事件是否阻断然后自身使用,如果返回False,将调用自身的dispatchTouchEvent来将事件分发给子View,如果是True,将会调用自身的OnTouchEvent来进行事件分类和处理。上面的代码实现了的轮播和点击,没有轮播图下面的索引点的切换,所以我继图片续自定义了一个ViewGroup继承FrameLayout来添加上面的ViewGroup和一个Linearalayout,在LinearLayout中添加索引的指示布局:
package com.gsww.www.scrollertest;
import android.content.Context;
import android.support.annotation.AttrRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
/**
* Author : luweicheng on 2017/3/24 0024 16:46
* E-mail :1769005961@qq.com
* GitHub : https://github.com/luweicheng24
*/
public class BannerFrameLayout extends FrameLayout implements ImageBannerViewGroup.BannerDotListener {
private ImageBannerViewGroup mImageBannerViewGroup;
private LinearLayout mLinearLayout;
public ImageBannerViewGroup getmImageBannerViewGroup() {
return mImageBannerViewGroup;
}
public void setmImageBannerViewGroup(ImageBannerViewGroup mImageBannerViewGroup) {
this.mImageBannerViewGroup = mImageBannerViewGroup;
}
public LinearLayout getmLinearLayout() {
return mLinearLayout;
}
public void setmLinearLayout(LinearLayout mLinearLayout) {
this.mLinearLayout = mLinearLayout;
}
private Integer[] banners;
public BannerFrameLayout(@NonNull Context context) {
this(context, null);
}
public BannerFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public BannerFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
initDragView();
initLinearLayout();
}
/**
* 设置Banner图的集合
*
* @param banners
* @param screenWidth 屏幕宽度
* @param screenHeight 屏幕高度
*/
public void addBanners(final Integer[] banners, int screenWidth, int screenHeight) {
this.banners = banners;
for (int i = 0; i < banners.length; i++) {
ImageView img = new ImageView(getContext());
img.setBackgroundResource(banners[i]);
img.setId(i);
ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(screenWidth * banners.length, screenHeight / 4);
img.setScaleType(ImageView.ScaleType.CENTER_CROP);
img.setLayoutParams(lp);
mImageBannerViewGroup.addView(img);
}
addDotToLinearLayout(banners.length);
}
private void addDotToLinearLayout(int dotCount) {
for (int i = 0; i < dotCount; i++) {
ImageView dot = new ImageView(getContext());
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
lp.setMargins(5, 0, 0, 0);
dot.setBackgroundResource(R.drawable.dot_drawable);
dot.setLayoutParams(lp);
mLinearLayout.addView(dot);
}
}
private void initLinearLayout() {
mLinearLayout = new LinearLayout(getContext());
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 100);
lp.gravity = Gravity.BOTTOM;
mLinearLayout.setLayoutParams(lp);
mLinearLayout.setGravity(Gravity.CENTER);
addView(mLinearLayout);
}
private void initDragView() {
mImageBannerViewGroup = new ImageBannerViewGroup(getContext());
ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
mImageBannerViewGroup.setLayoutParams(lp);
addView(mImageBannerViewGroup);
mImageBannerViewGroup.setBannerDotListener(this);
}
@Override
public void bannerDotListener(int pos) {
for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
if (pos == i) {
ImageView img = (ImageView) mLinearLayout.getChildAt(i);
img.setBackgroundResource(R.drawable.dot_select);
} else {
ImageView img = (ImageView) mLinearLayout.getChildAt(i);
img.setBackgroundResource(R.drawable.dot_no_select);
}
}
}
}
在自定义FrameLayout中我初始化了图片轮播的ViewGroup和索引布局LinearLayout,在使用的时候如下布局和使用:
<?xml version="1.0" encoding="utf-8"?>
<com.gsww.www.scrollertest.BannerFrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/mBannerFrameLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</com.gsww.www.scrollertest.BannerFrameLayout>
package com.gsww.www.scrollertest;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.DisplayMetrics;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity implements ImageBannerViewGroup.BannerImagClickListener {
private BannerFrameLayout banner;
private Integer[] imgs = {R.drawable.girl_3, R.drawable.girl_5, R.drawable.girl_6,
R.drawable.girl_4, R.drawable.girl_7, R.drawable.girl_9};
private int screenWidth;
private int screenHeight;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().hide();
setContentView(R.layout.main);
banner = (BannerFrameLayout) findViewById(R.id.mBannerFrameLayout);
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
screenWidth = dm.widthPixels;
screenHeight = dm.heightPixels;
banner.addBanners(imgs, screenWidth, screenHeight);
banner.getmImageBannerViewGroup().setBannerImagClickListener(this);
}
@Override
public void bannerClick(int pos) {
Toast.makeText(this, ""+pos, Toast.LENGTH_SHORT).show();
}
}
好了,自定义轮播图就这样完成了,源码点击下载