邂逅
Android-CleanArchitecture 推荐,看到了这个略吊的项目名称EffectiveAndroidUI,3000+start果断把源码看遍。Android-CleanArchitecture 博文是Android初级架构的经典文章,建议有性趣的同学要看一下。
观后感
- 项目使用不是dagger2,而是用的是square的dagger略显过时;
- EffectiveAndroidUI 就是标题党,实践上是对ViewHolder抽象化,使用工厂(AdapterFactory)类来减小Adapter和ListUI界面的相互依赖关系。
优点:使Adapter相对的独立,抽离ViewHolder中的事件处理,配合Presenter,易于维护,代码脉络相对清晰。
缺点:较大的增加了代码的复杂度,建议项目初期不要使用,在项目进行架构优化是可以考虑引入。 - 使用DraggablePanel 实现YouTube的视频拖拽浮动的功能,感觉略吊。
- 像Android-CleanArchitecture使用了Navigator类来统一处理界面之间的导航关系
- Renderers 从项目提取的开源库 减少Adapter 和 List之间的耦合 ;
代码走读
项目使用三种布局来分别适配了低版本(v10),常用版本(v11+), 及平板的布局。
这个很重要,因为俺就被这搞糊涂了,一开始跟着v11入口看代码,快看完的时候发现MainActivity.java 有一个Fragment的初始化
private void initializeTvShowFragment() {
tvShowFragment = (TvShowFragment) getSupportFragmentManager().findFragmentById(R.id.f_tv_show);
}
然后就,有点懵了....
现在开始从V11开始
MVP
TvShowCatalogFragment/TvShowDraggableFragment使用的是MVP模式,一句话概括就是:
明确(定义接口)UI界面的功能,把UI的交互逻辑放入Presenter实现。
如果接触过一些老项目的同学应该会深有感悟,随着项目功能的增加Activity/Fragment越来越重,耦合越来越强,使代码复用变得很难,无法积累及实现模块化,敏捷的开发。
故MVP就流行起来了,MVP还有一个好处就是方便Unit test。最新的Android Testing Support Library 也提倡使用MVP,在官方的Demo:android-testing也使用MVP,强烈建议同学们去看这个测试入门Demo。
进阶阅读:MVVM wiki ,还有官网的Data Binding Guide , 过一边较好。
重点代码举例
抽离MVP,TvShowCatalogFragment只有AdapterFactory比较重要
private void initializeGridView() {
adapter = tvShowRendererAdapterFactory.getTvShowRendererAdapter(tvShows);
gv_tv_shows.setAdapter(adapter);
}
TvShowRendererAdapterFactory.java
public class TvShowRendererAdapterFactory {
private final TvShowRendererBuilder rendererBuilder;
private final LayoutInflater layoutInflater;
@Inject
public TvShowRendererAdapterFactory(TvShowRendererBuilder rendererBuilder,
LayoutInflater layoutInflater) {
this.rendererBuilder = rendererBuilder;
this.layoutInflater = layoutInflater;
}
public RendererAdapter<TvShow> getTvShowRendererAdapter(final TvShowCollection tvShowCollection) {
return new RendererAdapter<TvShow>(layoutInflater, rendererBuilder, tvShowCollection);
}
}
整个项目名称是EffectiveAndroidUI其实略有点夸张,这里突然想起大学外面的小菜馆名字“重庆大饭店”,“某某洗脚城”.....。Effective的体现:
EffectiveAndroid.jpg RendererBuilder核心是通过RendererAdapterFactory,T 数据泛华,RendererBuilder 来解强耦合
动画简述
- 动画效果已经单独成库: DraggablePanel
动画实现的概述
动画主要通过使用ViewDragHelper, 界面布局使用一个Activity叠加两个Fragment来实现。 可以点击ViewDragHelper 简单初始化案例;
DraggableView
初始化
@Override protected void onFinishInflate() {
super.onFinishInflate();
if (!isInEditMode()) {
mapGUI(); //DragView ListView 初始化
initializeTransformer(); // Transform 管理类的初始化化
initializeViewDragHelper();
}
}
重要方法的逻辑翻译
private boolean smoothSlideTo(float slideOffset) {
final int topBound = getPaddingTop();
//(父控件参数 - dragView最小化参数) * 偏移因数 = 实践偏移量
int x = (int) (slideOffset * (getWidth() - transformer.getMinWidthPlusMarginRight()));
int y = (int) (topBound + slideOffset * getVerticalDragRange());
if (viewDragHelper.smoothSlideViewTo(dragView, x, y)) {
ViewCompat.postInvalidateOnAnimation(this);
return true;
}
return false;
}
onTouchEvent
@Override public boolean onTouchEvent(MotionEvent ev) {
...
if (activePointerId == INVALID_POINTER) { //是否是无效的点,是不做操作
return false;
}
viewDragHelper.processTouchEvent(ev); //把事件交给ViewDragHelper处理,处理完触发回调
if (isClosed()) { //是否是已经关闭,可拖动的View不在屏幕显示。是不做操作
return false;
}
//event X, Y 加上parentView的坐标偏移,是否在ChildView 中
boolean isDragViewHit = isViewHit(dragView, (int) ev.getX(), (int) ev.getY());
boolean isSecondViewHit = isViewHit(secondView, (int) ev.getX(), (int) ev.getY());
//Down事件和Up事件X坐标的变化量,当变化量大于指定值最大或最小化dragView
analyzeTouchToMaximizeIfNeeded(ev, isDragViewHit);
if (isMaximized()) {
dragView.dispatchTouchEvent(ev);
} else {
dragView.dispatchTouchEvent(cloneMotionEventWithAction(ev, MotionEvent.ACTION_CANCEL));
}
return isDragViewHit || isSecondViewHit;
}
clampViewPositionHorizontal
@Override public int clampViewPositionHorizontal(View child, int left, int dx) {
int newLeft = draggedView.getLeft();
//当可以拖动的View最小化并且X轴的速度大于某个速度,
//或可以拖动的View在底部,但是没有靠右时更新left的值
if ((draggableView.isMinimized() && Math.abs(dx) > MINIMUM_DX_FOR_HORIZONTAL_DRAG) || (
draggableView.isDragViewAtBottom()
&& !draggableView.isDragViewAtRight())) {
newLeft = left;
}
return newLeft;
}
clampViewPositionVertical
@Override public int clampViewPositionVertical(View child, int top, int dy) {
int newTop = draggableView.getHeight() - draggableView.getDraggedViewHeightPlusMarginTop();
//当可以拖动的View最小化并且Y轴的速度大于某个速度,
//或可以拖动的View在底部,但是没有靠右时更新Top的值
if (draggableView.isMinimized() && Math.abs(dy) >= MINIMUM_DY_FOR_VERTICAL_DRAG
|| (!draggableView.isMinimized() && !draggableView.isDragViewAtBottom())) {
final int topBound = draggableView.getPaddingTop();
final int bottomBound = draggableView.getHeight()
- draggableView.getDraggedViewHeightPlusMarginTop()
- draggedView.getPaddingBottom();
// bottomBound <= top <= topBound
newTop = Math.min(Math.max(top, topBound), bottomBound);
}
return newTop;
}
onViewPositionChanged change scale/alpha/position
triggerOnReleaseActionsWhileVerticalDrag
private void triggerOnReleaseActionsWhileVerticalDrag(float yVel) {
if (yVel < 0 && yVel <= -Y_MIN_VELOCITY) { //Y速度向上
draggableView.maximize();
} else if (yVel > 0 && yVel >= Y_MIN_VELOCITY) {//Y速度向向下
draggableView.minimize();
} else {
if (draggableView.isDragViewAboveTheMiddle()) { //y轴的拖动范围大于一半时
draggableView.maximize();
} else {
draggableView.minimize();
}
}
}
网友评论