鸿蒙开发之Banner封装,使用简单

简介: 鸿蒙开发之Banner封装,使用简单

背景

鸿蒙开发也有一段时间了,前端时间忙于项目,没有时间整理,如今项目完成,开始给大家分享下自己封装的轮子,便于大家更快的入坑,下面给大家分享下最常用的轮播图如何封装



使用方法

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();

效果图

image.pngbanner实际效果图.gif



下面所要讲到的内容有
一、 图片轮播原理
二、 指示器轮播原理
三、 触摸时,轮播图停止翻滚页面
四、 静态内部类+弱引用方式实现通知效果
五、PagerAdapter如何书写

原理解析

一、图片轮播原理

轮廓

android轮播控件通常由ViewPager和指示器组成。鸿蒙实现原理和android类似,用类似viewPager控件PageSlider和指示器组成

分析

PageSliderviewPager一样,不支持循环翻页,想要达到循环翻页效果,需要自己逻辑实现,我们看到的无限循环效果,实则是页面切换无动画,就达到了这样效果,原理如下:

PageSlider在第一项和最后一项时不能向前或向后滑动,那我们可以在原列表首尾各增加一项。增加首项为原列表的最后一项,尾项为原列表的第一项。并在ViewPager切换到边界页时,静默地更改到第二页或倒数第二页,就可以循环滚动切换了。使用了ViewPage.setCurrentPage(int itemPos, boolean smoothScroll)立即切换页面。

图片查看更直观

1688294759753.png


图片原理.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);
    }
}


相关文章
|
6天前
|
Android开发
鸿蒙开发:自定义一个简单的标题栏
本身就是一个很简单的标题栏组件,没有什么过多的技术含量,有一点需要注意,当使用沉浸式的时候,注意标题栏的位置,需要避让状态栏。
鸿蒙开发:自定义一个简单的标题栏
|
6天前
|
API
鸿蒙开发:切换至基于rcp的网络请求
本文的内容主要是把之前基于http封装的库,修改为当前的Remote Communication Kit(远场通信服务),无非就是通信的方式变了,其他都大差不差。
鸿蒙开发:切换至基于rcp的网络请求
|
11天前
|
UED
鸿蒙next版开发:相机开发-适配不同折叠状态的摄像头变更(ArkTS)
在HarmonyOS 5.0中,ArkTS提供了强大的相机开发能力,特别是针对折叠屏设备的摄像头适配。本文详细介绍了如何在ArkTS中检测和适配不同折叠状态下的摄像头变更,确保相机应用在不同设备状态下的稳定性和用户体验。通过代码示例展示了具体的实现步骤。
39 8
|
11天前
|
API 内存技术
鸿蒙next版开发:相机开发-拍照(ArkTS)
在HarmonyOS 5.0中,ArkTS提供了一套完整的API来管理相机功能,特别是拍照功能。本文详细介绍如何在ArkTS中实现拍照功能,包括导入接口、创建会话、配置会话、触发拍照及监听拍照输出流状态,并提供代码示例进行详细解读。通过本文,你将掌握如何在HarmonyOS 5.0中使用ArkTS实现高效的拍照功能。
31 7
|
11天前
|
前端开发 API
鸿蒙next版开发:相机开发-预览(ArkTS)
在HarmonyOS 5.0中,使用ArkTS进行相机预览是核心功能之一。本文详细介绍了如何使用ArkTS实现相机预览,包括导入相机接口、创建Surface、获取相机输出能力、创建会话并开始预览,以及监听预览输出状态等步骤,并提供了代码示例。通过本文,读者可以掌握在HarmonyOS 5.0中使用ArkTS进行相机预览的基本方法。
32 6
|
11天前
|
编解码 开发工具 计算机视觉
鸿蒙5.0版开发:命令行工具(mediatool工具)
在HarmonyOS 5.0的开发中,命令行工具mediatool基于FFmpeg库,提供了丰富的媒体处理功能,如视频和音频的转码、封装格式转换、提取媒体信息等。本文详细介绍mediatool的功能和使用方法,并提供代码示例。
31 6
|
11天前
|
前端开发 开发者
鸿蒙next版开发:相机开发-元数据(ArkTS)
在HarmonyOS 5.0中,ArkTS新增了对相机元数据的访问能力,帮助开发者获取图像的详细信息。本文介绍了如何在ArkTS中获取和使用相机元数据,包括导入接口、创建元数据输出流、开启和停止元数据输出、监听元数据对象可用事件等步骤,并提供了详细的代码示例。
37 5
|
11天前
|
前端开发 API 开发者
鸿蒙next版开发:相机开发-录像(ArkTS)
在HarmonyOS 5.0中,ArkTS提供了一套完整的API来管理相机录像功能。本文详细介绍了如何在ArkTS中实现录像功能,包括导入接口、创建Surface、获取相机输出能力、创建会话并开始录像以及监听录像输出流状态,并提供了代码示例进行解读。希望本文能帮助开发者更好地利用ArkTS的相机录像功能。
29 5
|
11天前
|
API 开发者 内存技术
鸿蒙next版开发:相机开发-会话管理(ArkTS)
在HarmonyOS 5.0中,ArkTS提供了完整的API来管理相机会话,包括创建相机输入流、预览输出流、拍照输出流,配置和管理会话。本文详细介绍了相机会话管理的基础步骤和代码示例,涵盖会话创建、闪光灯和焦距配置及错误处理等内容,帮助开发者更好地利用ArkTS开发相机应用。
34 4
|
11天前
|
UED
鸿蒙next版开发:音频并发策略扩展(ArkTS)
在HarmonyOS 5.0中,音频并发策略通过ArkTS的AudioSessionManager接口管理多个音频流的交互和优先级。本文介绍了如何自定义音频焦点策略,包括激活、停用音频会话及注册回调函数,并提供了示例代码。适用于多媒体、通信和游戏应用。
37 4

热门文章

最新文章