创建类ImageBannerViewGroup继承ViewGroup
继承ViewGroup必须要实现布局onLayout方法
且要
实现以下三个构造方法即可
public ImageBannerViewGroup(Context context)
public ImageBannerViewGroup(Context context, AttributeSet attrs)
public ImageBannerViewGroup(Context context, AttributeSet attrs, int defStyleAttr)
我们在自定义ViewGroup中,我们必须要实现的方法有:测量-》布局一》绘制
对于测量来说就是:onMeasure()
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//由于我们要实现的是一个ViewGroup的容器,那么
//我们就应该需要知道该容器中的所有子视图
//我们要想测量我们的ViewGroup的宽度和高度,那么
//我们就必须先要去测量子视图的宽度和高度之和,
//才能知道我们的ViewGroup的宽度和高度是多少
//1、求出子视图的个数
int children = getChildCount();//通过这个方法可以得到子视图的个数
if (children == 0) {
//这是宽度和高度
setMeasuredDimension(0, 0);
} else {
//2、测量子视图的宽度和高度
measureChildren(widthMeasureSpec, heightMeasureSpec);
//此时我们以第一个子视图为基准,也就是说
//我们的ViewGroup的高度就是我们第一个子视图的高度,
//宽度就是我们第一个子视图的宽度*子视图的个数
View view = getChildAt(0);//第一个视图一定存在
//3、根据子视图的宽度和高度,来求出该viewGroup的宽度和高度
int height = view.getMeasuredHeight();
int width = view.getMeasuredWidth() * children;
//将宽度和高度重新赋值给ViewGroup
setMeasuredDimension(width,height);
}
}
布局就是:onLayout()
/**
* 继承ViewGroup必须要实现布局onLayout方法
* @param changed 代表的时候是当我们的ViewGroup布局位置发生改变的为true,没有发生发生改变为false
* @param l left
* @param t to
* @param r right
* @param b bottom
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//如果位置发生改变
if (changed){
int leftMargin = 0;
for (int i = 0; i < children; i++) {
View view = getChildAt(i);
l = leftMargin;
t = 0;
r = leftMargin+childwidth;
b = childheight;
view.layout(l, t, r, b);//为每一个子视图布局
leftMargin+=childwidth;
}
}
}
我们对于绘制来说,因为我们是自定义的viewGroup容器,针对于容器的绘制,
其实就是容器内的子控件的绘制过程,那么我们只需要调用系统自带的绘制即可,
也就说,对于viewGroup绘制过程我们不需要再重写该方法,调用系统自带的即可。
/**
* 事件的传递过程中的调用方法:我们需要调用容器的拦载方法 onInterceptTouchEvent
* 针对于该方法我们可以理解为如果说该方法的返回值为true的时候,那么我们自定义的viewGroup容器就会处理此次拦截事件
* 如果说返回值为false的时候,那么我们自定义的viewGroup容器将不会接受此次事件的处理过程,将会继续向下传递该事件,
* 针对于我们自定义的ViewGroup 我们当然是希望我们的ViewGroup 容器处理接受事件那么我们的返回值就是true
* 如果返回true的话,真正处理该事件的方法 onTouchEvent
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// return super.onInterceptTouchEvent(ev);
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN://表示的是用户按下的一瞬间
break;
case MotionEvent.ACTION_MOVE://表示的是用户按下之后在屏幕上移动的过程
break;
case MotionEvent.ACTION_UP://表示的是用户抬起的一瞬间
break;
default:
break;
}
// return super.onTouchEvent(event);
return true;//返回true的目的是告诉 我们该Viewgroup容器的父View 我们已经处理好了该事件。
}
第一种方法:利用scrollTo、scrollBy完成轮播图的手动轮播
继续完善onTouchEvent(MotionEvent event)方法
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {//表示的是用户按下的一瞬间
x = (int) event.getX();
break;
}
case MotionEvent.ACTION_MOVE: {//表示的是用户按下之后在屏幕上移动的过程
int moveX = (int) event.getX();
int distance = moveX - x;//移动的距离
scrollBy(-distance, 0);
x = moveX;
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;
}
scrollTo(index * childwidth, 0);//滑动设置
break;
}
default: {
break;
}
}
// return super.onTouchEvent(event);
return true;//返回true的目的是告诉 我们该Viewgroup容器的父View 我们已经处理好了该事件。
}
第二种方法:利用Scroller完成轮播图的手动轮播
在ImageBannerViewGroup类中创建对象:Scroller
private Scroller scroller;
第二种方法第一步:
//第二种方法第一步:初始化scroller对象,在三个构造方法中调用
private void initObj() {
scroller = new Scroller(getContext());
}
第二种方法第二步:
//第二种方法第二步:使用scroller对象的使用自定义ViewGroup中实现computeScroll方法
@Override
public void computeScroll() {
super.computeScroll();
if (scroller.computeScrollOffset()) {
scrollTo(scroller.getCurrX(), 0);
invalidate();
}
}
第二种方法第三步:
修改onTouchEvent(MotionEvent event)方法
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {//表示的是用户按下的一瞬间
//第二种方法优化:
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;
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;
}
//第二种方法第三步:
int dx = index * childwidth - scrollX;
scroller.startScroll(scrollX, 0, dx, 0);
postInvalidate();
//第一种方法
// scrollTo(index * childwidth, 0);//滑动设置
break;
}
default: {
break;
}
}
// return super.onTouchEvent(event);
return true;//返回true的目的是告诉 我们该Viewgroup容器的父View 我们已经处理好了该事件。
}
下面贴出MainImageBannerActivity.java的代码
public class MainImageBannerActivity extends AppCompatActivity {
private ImageBannerViewGroup mGroup;
private int[] ids = new int[]{//图片的索引
R.drawable.logo,
R.drawable.love,
R.drawable.logo
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_image_banner);
//我们需要计算出当前手机的宽度
DisplayMetrics dm=new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
int width = dm.widthPixels;
mGroup = (ImageBannerViewGroup)findViewById(R.id.image_group);
for (int i = 0; i < ids.length; i++) {
ImageView imageView = new ImageView(this);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
//设置imageView的宽度和高度
imageView.setLayoutParams(new ViewGroup.LayoutParams(width,ViewGroup.LayoutParams.WRAP_CONTENT));
imageView.setImageResource(ids[i]);
mGroup.addView(imageView);
}
}
}
实现自动轮播:
采用Timer,TimerTask,Handler三者相结合的方式来实现自动轮播
抽出2个方法来控制,是否启动自动轮播 我们称之为startAuto(),stopAuto();
那么在2个方法的控制过程中,实际上希望控制的是自动开启轮播图的开关
那么就需要一个变量参效来作为自动开启轮播图的开关,称之为isAtuo boolean true代表开启,falset表关闭
//自动轮播
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);
break;
}
}
}
};
private void startAuto() {
isAuto = true;
}
private void stopAuto() {
isAuto = false;
}
由于initObj()方法在构造方法中调用,所以我们将task和timer代码写在该方法中
private void initObj() {
scroller = new Scroller(getContext());
task = new TimerTask() {
@Override
public void run() {
if (isAuto) {//说明已经开启了自动轮播
autoHandler.sendEmptyMessage(0);
}
}
};
timer.schedule(task, 100, 1000);//100毫秒中,每隔1秒执行task任务
}
ViewGroup自动轮播实现小bug修正:
要想实现图片的单击事件的获取
我们采用的方法就是利用一个单击变量开关进行判断
在用户离开屏幕的一瞬间我们去获取变量开关来判断用户的操作是点击还是移动
private boolean isClick;//true代表点击事件,false代表不是点击事件
定义一个ImageBannerLister接口:
public interface ImageBannerLister {
void clickImageIndex(int pos);//pos代表的是当前图片的具体索引值
}
添加变量private ImageBannerLister lister并设置get、set方法:
private ImageBannerLister lister;
//get/set
public ImageBannerLister getLister() {
return lister;
}
public void setLister(ImageBannerLister lister) {
this.lister = lister;
}
用户事件中设置
case MotionEvent.ACTION_DOWN: {//表示的是用户按下的一瞬间
isClick = true;
}
case MotionEvent.ACTION_MOVE: {//表示的是用户按下之后在屏幕上移动的过程
isClick = false;
}
case MotionEvent.ACTION_UP: {//表示的是用户抬起的一瞬间
if (isClick) {//代表是点击事件
lister.clickImageIndex(index);
} else {
//第二种方法第三步:
int dx = index * childwidth - scrollX;
scroller.startScroll(scrollX, 0, dx, 0);
postInvalidate();
//第一种方法
// scrollTo(index * childwidth, 0);//滑动设置
}
}
在中MainImageBannerActivity类中实现接口实现方法,并在onCreate方法中调用setLister()方法
public class MainImageBannerActivity
extends AppCompatActivity
implements ImageBannerViewGroup.ImageBannerLister
@Override
public void clickImageIndex(int pos) {
Toast.makeText(this,"pos = "+pos,Toast.LENGTH_SHORT).show();
}
mGroup.setLister(this);//在onCreate中调用