背景
鸿蒙开发也有一段时间了,前端时间忙于项目,没有时间整理,如今项目完成,开始给大家分享下自己封装的轮子,便于大家更快的入坑,下面给大家分享下最常用的轮播图如何封装
使用方法
HmBanner hmBanner = (HmBanner) findComponentById(ResourceTable.Id_banner); hmBanner.setImages(arrayList) .setIsAutoLoop(true)//设置是否可以自动轮播 .setLoopTime(3000)//设置轮播间隔时间 .setStartIndex(0)//设置起始位置 .setImageLoader(new ImageLoader() { @Override public void displayImage(Context context, Object path, Image imageView) { GlideUtils.getInstance().displayImage(HMBannerSlice.this, String.valueOf(path), imageView); } }).start();
效果图
banner实际效果图.gif
下面所要讲到的内容有 |
一、 图片轮播原理 |
二、 指示器轮播原理 |
三、 触摸时,轮播图停止翻滚页面 |
四、 静态内部类+弱引用方式实现通知效果 |
五、 PagerAdapter 如何书写 |
原理解析
一、图片轮播原理
轮廓
android
轮播控件通常由ViewPager
和指示器组成。鸿蒙实现原理和android
类似,用类似viewPager
控件PageSlider
和指示器组成
分析
PageSlider
和viewPager
一样,不支持循环翻页,想要达到循环翻页效果,需要自己逻辑实现,我们看到的无限循环效果,实则是页面切换无动画,就达到了这样效果,原理如下:
当PageSlider
在第一项和最后一项时不能向前或向后滑动,那我们可以在原列表首尾各增加一项。增加首项为原列表的最后一项,尾项为原列表的第一项。并在ViewPager
切换到边界页时,静默地更改到第二页或倒数第二页,就可以循环滚动切换了。使用了ViewPage.setCurrentPage(int itemPos, boolean smoothScroll)
立即切换页面。
图片查看更直观
图片原理.png
再分析
有了图更直观些,
我们需要轮播n
张图,实则数据为n+2
张图,开头结尾个添加一个数据体,作用用来自动切换页面效果
假如当页面切换到最开始的4
,我们直接让它跳转到末尾的4
,不加切换动画效果
假如当页面切换到最末尾的1
,我们直接让它跳转到开始的1
,不加切换动画效果
so~ 就实现了我们无限轮播的效果了
关键代码如下
@Override public void onPageSlideStateChanged(int state) { switch (state) { case 0://No operation if (mCurrentItem == 0) { mPageSlider.setCurrentPage(mImageUrls.size(), false); } else if (mCurrentItem == mImageUrls.size() + 1) { mPageSlider.setCurrentPage(1, false); } break; case 1://start Sliding if (mCurrentItem == mImageUrls.size() + 1) { mPageSlider.setCurrentPage(1, false); } else if (mCurrentItem == 0) { mPageSlider.setCurrentPage(mImageUrls.size(), false); } break; case 2://end Sliding break; } }
二、指示器轮播原理
在上边我们已经实现了图片轮播,指示器切换就简单多了,拿到自己计算的index
,进行切换即可,指示器我是用DirectionalLayout
去动态添加子Components
原理去实现的,核心代码如下
private void createIndicator(int count) { mIndicatorImages.clear(); mDirectionalLayout.removeAllComponents(); for (int i = 0; i < count; i++) { Image imageView = new Image(mContext); imageView.setWidth(30); imageView.setHeight(30); imageView.setMarginLeft(10); imageView.setMarginRight(10); imageView.setScaleMode(Image.ScaleMode.CLIP_CENTER); if (i == 0) { imageView.setPixelMap(black); } else { imageView.setPixelMap(gray); } mIndicatorImages.add(imageView); mDirectionalLayout.addComponent(imageView); } }
通过代码可以直观看出,动态添加Components
,然后移除再动态添加,这个只要page
的下标计算的没问题,这个很简单就可以实现了,如果后续需要其他特殊效果,原理一样,可以在这个方法中进行扩展。
三、触摸时,轮播图停止翻滚页面
说起触摸就要需要监听触摸事件
mPageSlider.setTouchEventListener(this);
@Override public boolean onTouchEvent(Component component, TouchEvent touchEvent) { if (mIsAutoLoop) { int action = touchEvent.getAction(); if (action == TouchEvent.CANCEL ||//指示事件被中断或取消 action == TouchEvent.PRIMARY_POINT_UP //表示最后一根手指从屏幕上抬起。 ) { startAutoPlay(); } else if (action == TouchEvent.PRIMARY_POINT_DOWN) {//表示第一个手指接触屏幕 stopAutoPlay(); } } return true; }
/** * 开始轮播 */ public void startAutoPlay() { if (mIsAutoLoop) { stopAutoPlay(); handler.postTask(mLoopTask, mLoopTime); } } /** * 停止轮播 */ public void stopAutoPlay() { if (mIsAutoLoop) { handler.removeTask(mLoopTask); } }
这个原理很简单,触摸时,停止handler的通知,手指抬起继续轮播,这样就说到了Handler
的实现
四、静态内部类+弱引用方式实现通知效果
为啥用这种方式呢,目的为了防止内存泄露,书写方式是这样如下
static class AutoLoopTask implements Runnable { private final WeakReference<HmBanner> reference; AutoLoopTask(HmBanner banner) { this.reference = new WeakReference<>(banner); } @Override public void run() { HmBanner hmBanner = reference.get(); if (hmBanner != null && hmBanner.mIsAutoLoop) { hmBanner.mCurrentItem = hmBanner.mCurrentItem % (hmBanner.mImageUrls.size() + 1) + 1; if (hmBanner.mCurrentItem == 1) { hmBanner.mPageSlider.setCurrentPage(hmBanner.mCurrentItem, false); handler.postTask(hmBanner.mLoopTask); } else { hmBanner.mPageSlider.setCurrentPage(hmBanner.mCurrentItem); handler.postTask(hmBanner.mLoopTask, hmBanner.mLoopTime); } } } }
初始化
static EventHandler handler = new EventHandler(EventRunner.getMainEventRunner()); private void initView() { mLoopTask = new AutoLoopTask(this); }
五、PagerAdapter如何书写
这个和android
写法差不多,只是名字进行了更改
public class BannerPagerAdapter extends PageSliderProvider { private Context mContext; private List<Component> mList; public BannerPagerAdapter(Context context, List<Component> list) { this.mContext = context; this.mList = list; } //ViewPager总共有几个页面 @Override public int getCount() { return mList.size(); } //添加视图 @Override public Object createPageInContainer(ComponentContainer componentContainer, int position) { componentContainer.addComponent(mList.get(position)); Component mComponent = mList.get(position); return mComponent; } ////移除视图 @Override public void destroyPageFromContainer(ComponentContainer componentContainer, int i, Object o) { componentContainer.removeComponent((Component) o); } // //判断一个页面(View)是否与instantiateItem方法返回的Object一致 @Override public boolean isPageMatchToObject(Component component, Object o) { return component == o; } }
六、最后贴一下布局代码
<?xml version="1.0" encoding="utf-8"?> <DependentLayout xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:id="$+id:bannerContainer" ohos:height="match_parent" ohos:width="match_parent"> <PageSlider ohos:id="$+id:HM_PageSlider" ohos:height="match_parent" ohos:width="match_parent"/> <DirectionalLayout ohos:id="$+id:indicatorInside" ohos:height="30vp" ohos:align_parent_bottom="true" ohos:orientation="horizontal" ohos:alignment="center" ohos:width="match_parent"/> </DependentLayout>
全部代码如下、分为3个类
一、HmBanner
package com.loocan.hmbanner.library; import com.cctv.harmony.phone.baselibrary.utils.DisplayUtils; import com.loocan.hmbanner.ResourceTable; import ohos.agp.components.*; import ohos.app.Context; import ohos.eventhandler.EventHandler; import ohos.eventhandler.EventRunner; import ohos.multimodalinput.event.TouchEvent; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; /** * author : liupeng * date : 2021/1/7 * desc : banner * Q群 : 1084365075 */ public class HmBanner extends StackLayout implements PageSlider.PageChangedListener, Component.TouchEventListener { //布局集合 private List<Component> mComponents; //图片集合 private List mImageUrls; //适配器 private BannerPagerAdapter adapter; //布局 private int mRootLayoutResId = ResourceTable.Layout_banner; private PageSlider mPageSlider; private DirectionalLayout mDirectionalLayout; //当前位置 private int mCurrentItem; //最后的位置 private int lastPosition; //设置起始位置 private int mStartIndex; // 是否自动轮播 private boolean mIsAutoLoop = true; // 轮播切换间隔时间 private long mLoopTime = 2000; //线程 private AutoLoopTask mLoopTask; private ImageLoaderInterface mImageLoaderInterface; //指示器集合 private List<Image> mIndicatorImages; static EventHandler handler = new EventHandler(EventRunner.getMainEventRunner()); private int black = ResourceTable.Media_banner_select; private int gray = ResourceTable.Media_banner_unselect; public HmBanner(Context context) { super(context); } public HmBanner(Context context, AttrSet attrSet) { super(context, attrSet); initView(); } public HmBanner(Context context, AttrSet attrSet, String styleName) { super(context, attrSet, styleName); initView(); } private void initView() { mComponents = new ArrayList<>(); mImageUrls = new ArrayList<>(); mIndicatorImages = new ArrayList<>(); mLoopTask = new AutoLoopTask(this); Component view = LayoutScatter.getInstance(mContext).parse(mRootLayoutResId, this, true); mPageSlider = (PageSlider) view.findComponentById(ResourceTable.Id_HM_PageSlider); mDirectionalLayout = (DirectionalLayout) view.findComponentById(ResourceTable.Id_indicatorInside); mPageSlider.setTouchEventListener(this); } public HmBanner start() { setImageList(mImageUrls); setData(); return this; } private void setData() { //设置起始位置 if (mStartIndex != 0) { mCurrentItem = mStartIndex++; } else { mCurrentItem = 1; } //设置适配器 setAdapter(); //设置当前下标 mPageSlider.setCurrentPage(mCurrentItem); if (mIsAutoLoop) { startAutoPlay(); } } /** * 设置adapter */ private void setAdapter() { if (adapter == null) { adapter = new BannerPagerAdapter(mContext, mComponents); mPageSlider.addPageChangedListener(this); mPageSlider.setProvider(adapter); } else { adapter.notifyDataChanged(); } } /** * 开始轮播 */ public void startAutoPlay() { if (mIsAutoLoop) { stopAutoPlay(); handler.postTask(mLoopTask, mLoopTime); } } /** * 停止轮播 */ public void stopAutoPlay() { if (mIsAutoLoop) { handler.removeTask(mLoopTask); } } static class AutoLoopTask implements Runnable { private final WeakReference<HmBanner> reference; AutoLoopTask(HmBanner banner) { this.reference = new WeakReference<>(banner); } @Override public void run() { HmBanner hmBanner = reference.get(); if (hmBanner != null && hmBanner.mIsAutoLoop) { hmBanner.mCurrentItem = hmBanner.mCurrentItem % (hmBanner.mImageUrls.size() + 1) + 1; if (hmBanner.mCurrentItem == 1) { hmBanner.mPageSlider.setCurrentPage(hmBanner.mCurrentItem, false); handler.postTask(hmBanner.mLoopTask); } else { hmBanner.mPageSlider.setCurrentPage(hmBanner.mCurrentItem); handler.postTask(hmBanner.mLoopTask, hmBanner.mLoopTime); } } } } @Override public void onPageSliding(int i, float v, int i1) { } @Override public void onPageSlideStateChanged(int state) { switch (state) { case 0://No operation if (mCurrentItem == 0) { mPageSlider.setCurrentPage(mImageUrls.size(), false); } else if (mCurrentItem == mImageUrls.size() + 1) { mPageSlider.setCurrentPage(1, false); } break; case 1://start Sliding if (mCurrentItem == mImageUrls.size() + 1) { mPageSlider.setCurrentPage(1, false); } else if (mCurrentItem == 0) { mPageSlider.setCurrentPage(mImageUrls.size(), false); } break; case 2://end Sliding break; } } @Override public void onPageChosen(int i) { mCurrentItem = i; mIndicatorImages.get((lastPosition - 1 + mImageUrls.size()) % mImageUrls.size()).setPixelMap(gray); mIndicatorImages.get((i - 1 + mImageUrls.size()) % mImageUrls.size()).setPixelMap(black); lastPosition = i; } @Override public boolean onTouchEvent(Component component, TouchEvent touchEvent) { if (mIsAutoLoop) { int action = touchEvent.getAction(); if (action == TouchEvent.CANCEL ||//指示事件被中断或取消 action == TouchEvent.PRIMARY_POINT_UP //表示最后一根手指从屏幕上抬起。 ) { startAutoPlay(); } else if (action == TouchEvent.PRIMARY_POINT_DOWN) {//表示第一个手指接触屏幕 stopAutoPlay(); } } return true; } /** * 设置数据源 * * @param imagesUrl */ private HmBanner setImageList(List<?> imagesUrl) { if (imagesUrl == null || imagesUrl.size() <= 0) { throw new NullPointerException("请设置数据源"); } createIndicator(imagesUrl.size()); // size:3 4 // index:0 url: c // index:1 url: a // index:2 url: b // index:3 url: c // index:4 url: a for (int i = 0; i <= imagesUrl.size() + 1; i++) { Object url = null; if (i == 0) { url = imagesUrl.get(imagesUrl.size() - 1); } else if (i == imagesUrl.size() + 1) { url = imagesUrl.get(0); } else { url = imagesUrl.get(i - 1); } Image image = null; if (image != null) { image = mImageLoaderInterface.createImageView(mContext, url); } if (image == null) { image = new Image(mContext); image.setWidth(DisplayUtils.getDisplayWidthInPx(mContext)); image.setHeight(getHeight()); image.setScaleMode(Image.ScaleMode.CLIP_CENTER); } mComponents.add(image); if (mImageLoaderInterface != null) { mImageLoaderInterface.displayImage(mContext, url, image); } else { throw new NullPointerException("请设置图片加载方法"); } } return this; } private void createIndicator(int count) { mIndicatorImages.clear(); mDirectionalLayout.removeAllComponents(); for (int i = 0; i < count; i++) { Image imageView = new Image(mContext); imageView.setWidth(30); imageView.setHeight(30); imageView.setMarginLeft(10); imageView.setMarginRight(10); imageView.setScaleMode(Image.ScaleMode.CLIP_CENTER); if (i == 0) { imageView.setPixelMap(black); } else { imageView.setPixelMap(gray); } mIndicatorImages.add(imageView); mDirectionalLayout.addComponent(imageView); } } /** * 设置图片加载器 * * @param imageLoader * @return */ public HmBanner setImageLoader(ImageLoaderInterface imageLoader) { this.mImageLoaderInterface = imageLoader; return this; } /** * 设置起始位置 * * @param startIndex * @return */ public HmBanner setStartIndex(int startIndex) { this.mStartIndex = startIndex; return this; } /** * 设置图片集合 * * @param ImageUrls * @return */ public HmBanner setImages(List<?> ImageUrls) { this.mImageUrls.addAll(ImageUrls); return this; } /** * 设置是否可以自动轮播 * * @param IsAutoLoop * @return */ public HmBanner setIsAutoLoop(boolean IsAutoLoop) { this.mIsAutoLoop = IsAutoLoop; return this; } /** * 设置轮播间隔时间 * * @param loopTime * @return */ public HmBanner setLoopTime(long loopTime) { mLoopTime = loopTime; return this; } public class BannerPagerAdapter extends PageSliderProvider { private Context mContext; private List<Component> mList; public BannerPagerAdapter(Context context, List<Component> list) { this.mContext = context; this.mList = list; } //ViewPager总共有几个页面 @Override public int getCount() { return mList.size(); } //添加视图 @Override public Object createPageInContainer(ComponentContainer componentContainer, int position) { componentContainer.addComponent(mList.get(position)); Component mComponent = mList.get(position); return mComponent; } ////移除视图 @Override public void destroyPageFromContainer(ComponentContainer componentContainer, int i, Object o) { componentContainer.removeComponent((Component) o); } // //判断一个页面(View)是否与instantiateItem方法返回的Object一致 @Override public boolean isPageMatchToObject(Component component, Object o) { return component == o; } } }
二、ImageLoader
import ohos.agp.components.Image; import ohos.app.Context; /** * author : liupeng * date : 2021/1/7 * desc : banner * Q群 : 1084365075 */ public abstract class ImageLoader implements ImageLoaderInterface<Image> { @Override public Image createImageView(Context context, Object path) { Image imageView = new Image(context); return imageView; } }
三、ImageLoaderInterface
import ohos.agp.components.Image; import ohos.app.Context; import java.io.Serializable; /** * author : liupeng * date : 2021/1/7 * desc : banner * Q群 : 1084365075 */ public interface ImageLoaderInterface<T extends Image> extends Serializable { void displayImage(Context context, Object path, T imageView); T createImageView(Context context, Object path); }
后记,反馈中提到,用到了一个工具类 DisplayUtils,我贴一下代码
/** * author : liupeng * date : 2021/1/5 * desc : 获取屏幕高宽 * Q群 : 1084365075 */ public class DisplayUtils { /** * 获取屏幕宽度 * * @param context 上下文 * @return 屏幕宽度 */ public static int getDisplayWidthInPx(Context context) { Display display = DisplayManager.getInstance().getDefaultDisplay(context).get(); Point point = new Point(); display.getSize(point); return (int) point.getPointX(); } /** * 获取屏幕高度,不包含状态栏的高度 * * @param context 上下文 * @return 屏幕高度,不包含状态栏的高度 */ public static int getDisplayHeightInPx(Context context) { Display display = DisplayManager.getInstance().getDefaultDisplay(context).get(); Point point = new Point(); display.getSize(point); return (int) point.getPointY(); } /** * vp转像素 * * @param context * @param vp * @return */ public static int vp2px(Context context, float vp) { DisplayAttributes attributes = DisplayManager.getInstance().getDefaultDisplay(context).get().getAttributes(); return (int) (attributes.densityPixels * vp); } }