抓住人生中的一分一秒,勝過(guò)虛度中的一月一年!
小做個(gè)動(dòng)圖開篇引題
背景
鴻蒙開發(fā)也有一段時(shí)間了从撼,前端時(shí)間忙于項(xiàng)目州弟,沒(méi)有時(shí)間整理,如今項(xiàng)目完成低零,開始給大家分享下自己封裝的輪子呆馁,便于大家更快的入坑,下面給大家分享下最常用的輪播圖如何封裝
使用方法
HmBanner hmBanner = (HmBanner) findComponentById(ResourceTable.Id_banner);
hmBanner.setImages(arrayList)
.setIsAutoLoop(true)//設(shè)置是否可以自動(dòng)輪播
.setLoopTime(3000)//設(shè)置輪播間隔時(shí)間
.setStartIndex(0)//設(shè)置起始位置
.setImageLoader(new ImageLoader() {
@Override
public void displayImage(Context context, Object path, Image imageView) {
GlideUtils.getInstance().displayImage(HMBannerSlice.this, String.valueOf(path), imageView);
}
}).start();
效果圖
下面所要講到的內(nèi)容有 |
---|
一毁兆、 圖片輪播原理 |
二、 指示器輪播原理 |
三阴挣、 觸摸時(shí)气堕,輪播圖停止翻滾頁(yè)面 |
四、 靜態(tài)內(nèi)部類+弱引用方式實(shí)現(xiàn)通知效果 |
五畔咧、 PagerAdapter 如何書寫 |
原理解析
一茎芭、圖片輪播原理
輪廓
android
輪播控件通常由ViewPager
和指示器組成。鴻蒙實(shí)現(xiàn)原理和android
類似誓沸,用類似viewPager
控件PageSlider
和指示器組成
分析
PageSlider
和viewPager
一樣梅桩,不支持循環(huán)翻頁(yè),想要達(dá)到循環(huán)翻頁(yè)效果拜隧,需要自己邏輯實(shí)現(xiàn)宿百,我們看到的無(wú)限循環(huán)效果,實(shí)則是頁(yè)面切換無(wú)動(dòng)畫洪添,就達(dá)到了這樣效果垦页,原理如下:
當(dāng)PageSlider
在第一項(xiàng)和最后一項(xiàng)時(shí)不能向前或向后滑動(dòng),那我們可以在原列表首尾各增加一項(xiàng)干奢。增加首項(xiàng)為原列表的最后一項(xiàng)痊焊,尾項(xiàng)為原列表的第一項(xiàng)。并在ViewPager
切換到邊界頁(yè)時(shí)忿峻,靜默地更改到第二頁(yè)或倒數(shù)第二頁(yè)薄啥,就可以循環(huán)滾動(dòng)切換了。使用了ViewPage.setCurrentPage(int itemPos, boolean smoothScroll)
立即切換頁(yè)面逛尚。
圖片查看更直觀
再分析
有了圖更直觀些垄惧,
我們需要輪播
n
張圖,實(shí)則數(shù)據(jù)為n+2
張圖黑低,開頭結(jié)尾個(gè)添加一個(gè)數(shù)據(jù)體赘艳,作用用來(lái)自動(dòng)切換頁(yè)面效果假如當(dāng)頁(yè)面切換到最開始的
4
酌毡,我們直接讓它跳轉(zhuǎn)到末尾的4
,不加切換動(dòng)畫效果假如當(dāng)頁(yè)面切換到最末尾的
1
蕾管,我們直接讓它跳轉(zhuǎn)到開始的1
枷踏,不加切換動(dòng)畫效果so~ 就實(shí)現(xiàn)了我們無(wú)限輪播的效果了
關(guān)鍵代碼如下
@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;
}
}
二、指示器輪播原理
在上邊我們已經(jīng)實(shí)現(xiàn)了圖片輪播掰曾,指示器切換就簡(jiǎn)單多了旭蠕,拿到自己計(jì)算的index
,進(jìn)行切換即可,指示器我是用DirectionalLayout
去動(dòng)態(tài)添加子Components
原理去實(shí)現(xiàn)的旷坦,核心代碼如下
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);
}
}
通過(guò)代碼可以直觀看出掏熬,動(dòng)態(tài)添加Components
,然后移除再動(dòng)態(tài)添加秒梅,這個(gè)只要page
的下標(biāo)計(jì)算的沒(méi)問(wèn)題旗芬,這個(gè)很簡(jiǎn)單就可以實(shí)現(xiàn)了,如果后續(xù)需要其他特殊效果捆蜀,原理一樣疮丛,可以在這個(gè)方法中進(jìn)行擴(kuò)展。
三辆它、觸摸時(shí)誊薄,輪播圖停止翻滾頁(yè)面
說(shuō)起觸摸就要需要監(jiān)聽(tīng)觸摸事件
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) {//表示第一個(gè)手指接觸屏幕
stopAutoPlay();
}
}
return true;
}
/**
* 開始輪播
*/
public void startAutoPlay() {
if (mIsAutoLoop) {
stopAutoPlay();
handler.postTask(mLoopTask, mLoopTime);
}
}
/**
* 停止輪播
*/
public void stopAutoPlay() {
if (mIsAutoLoop) {
handler.removeTask(mLoopTask);
}
}
這個(gè)原理很簡(jiǎn)單锰茉,觸摸時(shí)呢蔫,停止handler的通知,手指抬起繼續(xù)輪播飒筑,這樣就說(shuō)到了Handler
的實(shí)現(xiàn)
四片吊、靜態(tài)內(nèi)部類+弱引用方式實(shí)現(xiàn)通知效果
為啥用這種方式呢,目的為了防止內(nèi)存泄露协屡,書寫方式是這樣如下
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如何書寫
這個(gè)和android
寫法差不多,只是名字進(jìn)行了更改
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總共有幾個(gè)頁(yè)面
@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);
}
// //判斷一個(gè)頁(yè)面(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個(gè)類
一、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;
//當(dāng)前位置
private int mCurrentItem;
//最后的位置
private int lastPosition;
//設(shè)置起始位置
private int mStartIndex;
// 是否自動(dòng)輪播
private boolean mIsAutoLoop = true;
// 輪播切換間隔時(shí)間
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() {
//設(shè)置起始位置
if (mStartIndex != 0) {
mCurrentItem = mStartIndex++;
} else {
mCurrentItem = 1;
}
//設(shè)置適配器
setAdapter();
//設(shè)置當(dāng)前下標(biāo)
mPageSlider.setCurrentPage(mCurrentItem);
if (mIsAutoLoop) {
startAutoPlay();
}
}
/**
* 設(shè)置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) {//表示第一個(gè)手指接觸屏幕
stopAutoPlay();
}
}
return true;
}
/**
* 設(shè)置數(shù)據(jù)源
*
* @param imagesUrl
*/
private HmBanner setImageList(List<?> imagesUrl) {
if (imagesUrl == null || imagesUrl.size() <= 0) {
throw new NullPointerException("請(qǐng)?jiān)O(shè)置數(shù)據(jù)源");
}
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("請(qǐng)?jiān)O(shè)置圖片加載方法");
}
}
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);
}
}
/**
* 設(shè)置圖片加載器
*
* @param imageLoader
* @return
*/
public HmBanner setImageLoader(ImageLoaderInterface imageLoader) {
this.mImageLoaderInterface = imageLoader;
return this;
}
/**
* 設(shè)置起始位置
*
* @param startIndex
* @return
*/
public HmBanner setStartIndex(int startIndex) {
this.mStartIndex = startIndex;
return this;
}
/**
* 設(shè)置圖片集合
*
* @param ImageUrls
* @return
*/
public HmBanner setImages(List<?> ImageUrls) {
this.mImageUrls.addAll(ImageUrls);
return this;
}
/**
* 設(shè)置是否可以自動(dòng)輪播
*
* @param IsAutoLoop
* @return
*/
public HmBanner setIsAutoLoop(boolean IsAutoLoop) {
this.mIsAutoLoop = IsAutoLoop;
return this;
}
/**
* 設(shè)置輪播間隔時(shí)間
*
* @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總共有幾個(gè)頁(yè)面
@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);
}
// //判斷一個(gè)頁(yè)面(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);
}
后記余蟹,反饋中提到卷胯,用到了一個(gè)工具類 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();
}
/**
* 獲取屏幕高度威酒,不包含狀態(tài)欄的高度
*
* @param context 上下文
* @return 屏幕高度窑睁,不包含狀態(tài)欄的高度
*/
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轉(zhuǎn)像素
*
* @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);
}
}
以上便是輪播圖所有代碼挺峡,祝大家開發(fā)愉快!
CSDN下載地址:https://download.csdn.net/download/loocanp/17882051