在RN中已經(jīng)提供了好多UI組件供開發(fā)者使用,社區(qū)里也有很多第三方的開源的UI組件可供開發(fā)者使用。 但是在應(yīng)用開發(fā)的過程中汪诉,我們難免需要自定義UI組件來滿足需求。
本文主要通過實(shí)現(xiàn)一個(gè)滑動(dòng)(雙擊)放大谈秫、縮小圖片得到組件來分享下如何創(chuàng)建一個(gè)UI Component扒寄。
正文之前
剛開始我只是看了官網(wǎng)的這篇文章然而并沒有去看系統(tǒng)的組件(Image,ScrollView 等) 的實(shí)現(xiàn)拟烫。想到要做一個(gè)能夠監(jiān)測手勢去放大縮小圖片的時(shí)候我的第一個(gè)想法就是“如何在RN中監(jiān)控该编、獲取用戶手勢”∷妒纾看完這些我已經(jīng)方了课竣,趕緊看下源碼中的ScrollView是如何處理手勢的。而實(shí)際情況是 RN 中只是通過 Native UI Component 的形式調(diào)用的Native 中 的ScrollView 置媳。至于手勢動(dòng)效都是 Native ScrollView自己的處理于樟。
后來想想才發(fā)現(xiàn)自己走了些許彎路,RN 最終就是渲染的 Native 組件拇囊。 Native組件本身的功能都是可以用的迂曲,我們只是通過RN Native UI Component的形式給 Native 組件設(shè)置一些初始的屬性。
實(shí)現(xiàn)思路
現(xiàn)在再想“如何去做一個(gè)滑動(dòng)(雙擊)放大\縮小圖片得到組件”寥袭,我們只要在Native中寫一個(gè)有此功能的組件路捧,通過RN使用一下這個(gè)組件就行了。想到 RN 中圖片加載已經(jīng)使用了Fresco框架传黄,而且已經(jīng)有一個(gè)基于Fresco的圖片放大縮小控件PhotoDraweeView杰扫。所以,我們可以直接用PhotoDraweeView尝江。思路明確后涉波,開始干。
Native中定義控件
在 Native 中炭序,就是一個(gè) PhotoDraweeView 啤覆,暴露兩個(gè)方法,一個(gè)用來在 JS 中通過Props設(shè)置控件的數(shù)據(jù)源(圖片地址)惭聂,另一個(gè)用來真正的加載圖片窗声。
public class ZoomableImageView extends PhotoDraweeView {
private Uri mUri = null;
public ZoomableImageView(Context context) {
super(context);
}
public void setSource(@Nullable String source, @NonNull ResourceDrawableIdHelper resourceDrawableIdHelper) {
if (source != null) {
try {
mUri = Uri.parse(source);
if (mUri.getScheme() == null) {
mUri = null;
}
} catch (Exception ignore) {
}
}
}
//from PhotoDraweeView demo
public void updateIfNeeded() {
if (mUri == null) {
return; //不關(guān)心占位圖
}
PipelineDraweeControllerBuilder controller = Fresco.newDraweeControllerBuilder();
controller.setUri(mUri);
controller.setOldController(getController());
// You need setControllerListener
controller.setControllerListener(new BaseControllerListener<ImageInfo>() {
@Override
public void onFinalImageSet(String id, ImageInfo imageInfo, Animatable animatable) {
super.onFinalImageSet(id, imageInfo, animatable);
if (imageInfo == null) {
return;
}
update(imageInfo.getWidth(), imageInfo.getHeight());
}
});
setController(controller.build());
setOnPhotoTapListener(new OnPhotoTapListener() {
@Override
public void onPhotoTap(View view, float x, float y) {
Toast.makeText(view.getContext(), "onPhotoTap : x = " + x + ";" + " y = " + y,
Toast.LENGTH_SHORT).show();
}
});
setOnViewTapListener(new OnViewTapListener() {
@Override
public void onViewTap(View view, float x, float y) {
Toast.makeText(view.getContext(), "onViewTap", Toast.LENGTH_SHORT).show();
}
});
setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
Toast.makeText(v.getContext(), "onLongClick", Toast.LENGTH_SHORT).show();
return true;
}
});
}
}
View Manager
如果你對(duì)View Manager不了解,可以參考官網(wǎng)的介紹辜纲。
View Manager主要決定組件的Name和它會(huì)暴露出去哪些屬性笨觅。
- createViewInstance方法創(chuàng)建具體的組件
- getName 方法決定了Component 的 Name 拦耐,通過JS調(diào)用的時(shí)候是通過Name來調(diào)用的
- @ReactProp 決定了Native暴露出的屬性與JS中控件props的對(duì)應(yīng)關(guān)系
- 值得一提的是onAfterUpdateTransaction 方法,這個(gè)回調(diào)會(huì)在所有有@ReactProp注解的方法更新完屬性后被調(diào)用见剩。我們通過它來加載具體的圖片杀糯。
public class ZoomableImageViewManager extends SimpleViewManager<ZoomableImageView> {
private static final String REACT_CLASS = "ZoomableImageViewAndroid";
private ResourceDrawableIdHelper mResourceDrawableIdHelper;
ZoomableImageViewManager(ReactApplicationContext context) {
mResourceDrawableIdHelper = new ResourceDrawableIdHelper();
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
protected ZoomableImageView createViewInstance(ThemedReactContext reactContext) {
return new ZoomableImageView(reactContext);
}
@ReactProp(name = "src")
public void setSource(ZoomableImageView view, @Nullable String source) {
view.setSource(source, mResourceDrawableIdHelper);
}
@Override
protected void onAfterUpdateTransaction(ZoomableImageView view) {
super.onAfterUpdateTransaction(view);
view.updateIfNeeded();
}
}
Register the ViewManager
將組件注冊(cè)到一個(gè)React Package中。
public class ZoomableImageViewPackage implements ReactPackage {
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
List<ViewManager> viewManagers = new ArrayList<>();
viewManagers.add(new ZoomableImageViewManager(reactContext));
return viewManagers;
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
Native初始化時(shí)加載對(duì)應(yīng)的React Package
初始化時(shí)需要初始化React Package苍苞,之后才能在 RN JS 中調(diào)用到此Native組件固翰。
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new ZoomableImageViewPackage(),
);
}
RN JS 中使用Native組件
export default class ZoomableImageView extends Component {
static propTypes = {
src: PropTypes.string,
...View.propTypes
};
render() {
if (this.props.src) {
console.warn('src is null');
}
return <ZoomableImageViewAndroid src={this.props.src} />
}
return null
}
}
const ZoomableImageViewAndroid = requireNativeComponent('ZoomableImageViewAndroid', ZoomableImageView);
最后,一個(gè)支持雙擊羹呵,雙指滑動(dòng)實(shí)現(xiàn)放大縮小的圖片瀏覽控件就實(shí)現(xiàn)了骂际。 它使用PhotoDraweeView來實(shí)現(xiàn)具體手勢監(jiān)聽和圖片縮放功能。PhotoDraweeView還有一些有用配置冈欢,如果要暴露給JS來配置歉铝,則要通過@ReactProp注解來暴露出來。
功能實(shí)現(xiàn)的同時(shí)凑耻,實(shí)際上是對(duì)Custom UI Component的加深理解太示。 Android本身控件庫已經(jīng)是非常豐富,通過Custom UI Component的形式香浩,可以快速實(shí)現(xiàn)在JS中使用Native組件先匪。
那么問題來了, RN中的ListView為何不是通過Native來實(shí)現(xiàn)的呢弃衍?