自定义View实现图片轮播,实现了图片自动轮播,手动滑动,轮播标题,以及点击事件。
里面有很多注释
/**
* Created by hp on 2018/7/31.
* 这是实现图片轮播的核心类
*/
public class ImageBannerViewGroup extends ViewGroup
{
private int children;//我们ViewGroup子视图的总个数
private int childwidth;//子视图的宽度
private int childheight;//子视图的高度
private int x;//此时的x的值 代表的是第一次按下的位置横坐标、每一次移动过程中一定之前的位置横坐标
private int index = 0;//代表的是我们每张图片的索引
private Scroller scroller;//.利用 Scroller 对象 完成轮播图的手动轮播
/**
* 要实现图片的单击事件的获取
* 我们 利用一个单击变量开关进行判断,在用户离开屏幕的一瞬间
* 我们用判断变量开关来判断用户的操作是点击还是移动
*/
private boolean isClick;//true代表的是点击事件,false代表的是不是点击事件
private ImageBarnnerLister lister;
public ImageBarnnerLister getLister(){
return lister;
}
public void setLister(ImageBarnnerLister lister) {
this.lister = lister;
}
public interface ImageBarnnerLister{
void clickImageIndex(int pos);//pos代表的是我们当前图片的具体索引值
}
private ImageBarnnerViewGroupLisnner barnnerViewGroupLisnner;
public ImageBarnnerViewGroupLisnner getBarnnerViewGroupLisnner() {
return barnnerViewGroupLisnner;
}
public void setBarnnerViewGroupLisnner(ImageBarnnerViewGroupLisnner barnnerViewGroupLisnner) {
this.barnnerViewGroupLisnner = barnnerViewGroupLisnner;
}
/**
* 实现图片轮播底部圆点以及底部圆点切换功能步骤思路:
* 1.自定义一个集成自FrameLayout的布局,利用FrameLayout布局的特性(在同一位置放置不同的iew最终显示的是最后一个view)
* 利用这个特性我们就可以实现底部圆点的布局
* 2.我们需要准备素材,就是底部圆点的素材,我们可以利用Drawable的功能,去实现一个圆点的展示
* 3.我们需要继承FrameLayout来定义一个类,在该类的实现过程中,我们去加载我们刚才自定义的ImageBannerViewGroup核心类
* 和我们需要实现的底部圆点的布局LinearLayout来实现
*/
//---自动轮播
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);
barnnerViewGroupLisnner.selectImage(index);
break;
}
}
};
private void startAuto(){
isAuto = true;
}
private void stopAuto(){
isAuto = false;
}
/**
* 采用Timer,TimerTask,Handler三者相结合的方法实现自动轮播
* 抽取两个方法来控制,是否启动自动轮播,称之为 startAuto(),stopAuto();
* 我们在2个方法的控制过程中,我们希望能有控制自动开启轮播图的开关
* 我们需要一个变量参数来作为我们自动启动轮播图的开关,为 isAuto boolean true代表开启,false代表关闭
*/
public ImageBannerViewGroup(Context context) {
super(context);
initObj();//.利用 Scroller 对象 完成轮播图的手动轮播需要用到这个方法,利用 scrollTo、scrollBy 完成轮播图的手动轮播时可以删掉
}
public ImageBannerViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
initObj();
}
public ImageBannerViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initObj();
}
private void initObj() {
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() {//利用 Scroller 对象 完成轮播图的手动轮播需要用到这个方法
super.computeScroll();
if(scroller.computeScrollOffset()){
scrollTo(scroller.getCurrX(),0);
invalidate();
}
}
/**
* 我们在自定义的ViewGroop中,我们必须要实现的方法有:测量-》布局-》绘制
* 那么对于来说就是:onMeasure()
* 我们对于绘制来说,因为我们是自定义的ViewGroup容器,针对于容器的绘制
* 其实就是容器内的子控件的绘制过程,那么我们只需要调用系统自带的绘制即可,
* 也就是对于ViewGroup绘制过程我们不需要再重写该方法
* 调用系统的即可
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/**
* 由于我们要实现ViewGroup的容器,
* 那么我们就需要知道该容器内的所有子视图
* 我们要想测量我们的ViewGroup的宽度和高度,那么我们就必须先要去测量子视图
* 的宽度和高度之和,才能知道我们的ViewGroup的宽度和高度是多少
*/
//1.求子视图的个数
children = getChildCount();//我们就可以知道子视图的个数
if(0 == children){
setMeasuredDimension(0,0);
}
else{
//2.测量子视图的高度
measureChildren(widthMeasureSpec,heightMeasureSpec);
//此时我们以第一个子视图为基准,也就是说我们的viewGroup的高度就是我们第一个子视图的高度
//宽度就是我们第一个姿势图的额宽度*子视图的个数
View view = getChildAt(0);//因为此时第一个视图绝对是存在的
childwidth = view.getMeasuredWidth();
//3.根据子视图的宽度和高度,求出改ViewGroup的宽度和高度
childheight = view.getMeasuredHeight();
int width = view.getMeasuredWidth() * children;
setMeasuredDimension(width,childheight);
}
}
/**
* 事件传递过程中的调用方法,我们需要调用容器的拦截方法 onInterceptTouchEvent
* 针对于该方法我们可以理解为 如果说该方法的返回值为true的时候,那么我们自定义的ViewGroup容器就会处理此次拦截事件
* 如果说返回值为false的时候,那么我们自定义的ViewGroup容器将不会接受此次事件的处理过程,将会继续向下传递事件
* 针对于我们自定义的ViewGroup 我们希望我们的ViewGroup,容器处理接受事件 那么我们的返回值就是true
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;
}
/**
* 用2中方式来实现轮播图的手动轮播
* 1.利用 scrollTo、scrollBy 完成轮播图的手动轮播
* 2.利用 Scroller 对象 完成轮播图的手动轮播
*
* 第一:我们在滑动屏幕图片的过程中,其实就是 我们自定义ViewGroup的子视图的移动过程,那么我们只需要知道
* 滑动之前的横坐标和滑动之后的横坐标,此时 我们就可以 求出次过程中移动的距离,我们在利用 scrollBy方法实现
* 图片的滑动,所以 此时我们需要有2个值要我们求出:移动之前、移动之后的 横坐标值
*
* 第二:在我们第一次按下的那一瞬间,此时的移动之前和移动之后的值是相等的,也就是我们按下的那一瞬间的那一个点
* 的横坐标的值。
*
* 第三:我们在不断滑动的过程中,会不断的调用我们的ACTION_MOVE方法,那么此时我们就应该将移动之前的值
* 和移动之后的进行保存,以便我们能够算出滑动的距离
*
* 第四:在我们抬起的那一瞬间,我们需要计算出我们此时将要滑动到哪张图片的位置上。
*
* 我们此时就需要求得将要滑动到的哪张图片的索引值。
* (我们当前ViewGroup的滑动距离 + 我们的每一张图片的宽度 / 2) / 我们的每一张图片的宽度值
*
* 此时我们就可以利用scrollTo方法。滑动到该图片的位置上
*
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
// return super.onTouchEvent(event);
switch (event.getAction()){
case MotionEvent.ACTION_DOWN://表示的是用户按下的一瞬间
stopAuto();
if(scroller.isFinished()){//.利用 Scroller 对象 完成轮播图的手动轮播
scroller.abortAnimation();
}
isClick = true;
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){//代表点击事件
lister.clickImageIndex(index);
}
else {
int dx = index * childwidth - scrollX;//.利用 Scroller 对象 完成轮播图的手动轮播
scroller.startScroll(scrollX,0,dx,0);//.利用 Scroller 对象 完成轮播图的手动轮播
postInvalidate();//.利用 Scroller 对象 完成轮播图的手动轮播
// scrollTo(index * childwidth,0);//利用 scrollTo、scrollBy 完成轮播图的手动轮播
barnnerViewGroupLisnner.selectImage(index);
}
startAuto();
break;
default:
break;
}
return true;//我们该容器的父View,我们已经处理好了该事件
}
/**
* 继承ViewGroup必须要实现布局onLayout方法
* @param changed 当我们的ViewGroup布局位置发生变化的为true,没有发生改变为false
* @param l
* @param t
* @param r
* @param b ,没有发生改变为false
*/
@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);
view.layout(leftMargin, 0, leftMargin + childwidth, childheight);
leftMargin += childwidth;
}
}
}
public interface ImageBarnnerViewGroupLisnner{
void selectImage(int index);
}
}
ImageBarnnerFrameLayout类
/**
* Created by hp on 2018/8/2.
*/
public class ImageBarnnerFrameLayout extends FrameLayout implements ImageBannerViewGroup.ImageBarnnerViewGroupLisnner,ImageBannerViewGroup.ImageBarnnerLister{
private ImageBannerViewGroup imageBannerViewGroup;
private LinearLayout linearLayout;
private FramLayoutLisenner lisenner;
public FramLayoutLisenner getLisenner() {
return lisenner;
}
public void setLisenner(FramLayoutLisenner lisenner) {
this.lisenner = lisenner;
}
public ImageBarnnerFrameLayout(@NonNull Context context) {
super(context);
initImageBannerViewGroup();
initDotLinearlayout();
}
public ImageBarnnerFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initImageBannerViewGroup();
initDotLinearlayout();
}
public ImageBarnnerFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initImageBannerViewGroup();
initDotLinearlayout();
}
public void addBitmaps(List list){
for(int i = 0;i < list.size();i++){
Bitmap bitmap = list.get(i);
addBitmapToImageBarnnerViewGroup(bitmap);
addDotToLinearlayout();
}
}
private void addDotToLinearlayout(){
ImageView iv = new ImageView(getContext());
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
lp.setMargins(5,5,5,5);
iv.setLayoutParams(lp);
iv.setImageResource(R.drawable.dot_normal);
linearLayout.addView(iv);
}
private void addBitmapToImageBarnnerViewGroup(Bitmap bitmap){
ImageView iv = new ImageView(getContext());
iv.setScaleType(ImageView.ScaleType.CENTER_CROP);
iv.setLayoutParams(new ViewGroup.LayoutParams(C.WITTH,ViewGroup.LayoutParams.WRAP_CONTENT));
iv.setImageBitmap(bitmap);
imageBannerViewGroup.addView(iv);
}
/**
* 初始化我们自定义的图片轮播功能的核心
*/
private void initImageBannerViewGroup(){
imageBannerViewGroup = new ImageBannerViewGroup(getContext());
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
imageBannerViewGroup.setLayoutParams(lp);
imageBannerViewGroup.setBarnnerViewGroupLisnner(this);//这里就是将Lisnner传递给Framlayout
imageBannerViewGroup.setLister(this);
addView(imageBannerViewGroup);
}
/**
* 初始化 底部圆点 布局
*/
private void initDotLinearlayout(){
linearLayout = new LinearLayout(getContext());
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,40);
linearLayout.setLayoutParams(lp);
linearLayout.setOrientation(LinearLayout.HORIZONTAL);
linearLayout.setGravity(Gravity.CENTER);
linearLayout.setBackgroundColor(Color.RED);
addView(linearLayout);
FrameLayout.LayoutParams layoutParams = (LayoutParams) linearLayout.getLayoutParams();
layoutParams.gravity = Gravity.BOTTOM;
linearLayout.setLayoutParams(layoutParams);
/**
* 设置透明度
* 在3.0以后,我们使用的是setAlpha(),在3.0之前我们使用的也是setAlpha(),但是调用者不同
*/
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
linearLayout.setAlpha(0.5f);
}
else {
linearLayout.getBackground().setAlpha(100);
}
}
@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) {
lisenner.clickImageIndex(pos);
}
public interface FramLayoutLisenner{
void clickImageIndex(int pos);
}
}
MainActivity类
public class MainActivity extends AppCompatActivity implements ImageBarnnerFrameLayout.FramLayoutLisenner {
private ImageBarnnerFrameLayout mGroup;
private int[] ids = new int[]{
R.drawable.d,
R.drawable.a,
R.drawable.b,
R.drawable.c
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//我们需要计算出我们当前手机的宽度
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
C.WITTH = dm.widthPixels;
mGroup = (ImageBarnnerFrameLayout)findViewById(R.id.image_group);
mGroup.setLisenner(this);
List list = new ArrayList<>();
for (int i = 0;i < ids.length;i++){
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),ids[i]);
list.add(bitmap);
}
mGroup.addBitmaps(list);
/* for (int i = 0;i < ids.length;i++){
ImageView iv = new ImageView(this);
iv.setScaleType(ImageView.ScaleType.CENTER_CROP);
iv.setLayoutParams(new ViewGroup.LayoutParams(width,ViewGroup.LayoutParams.WRAP_CONTENT));
iv.setImageResource(ids[i]);
mGroup.addView(iv);
}
mGroup.setLister(this);
*/
}
public void clickImageIndex(int pos){
Toast.makeText(this,"pos = "+pos,Toast.LENGTH_SHORT).show();
}
}
C类
public class C {
public static int WITTH = 0;
}
导包时要注意,有的包java和Android都有,出错可能就是导入了java的包
代码是根据慕课上的老师的课程写的,老师讲的很好,强烈建议大家去听,讲解的很详细,收获很大
老师课程链接https://www.imooc.com/learn/793