项目实战:硅谷金融APPday03学习笔记—自定义View 及 代码抽取
不否认努力,继续加油!
学习整理重点、盲区,笔记如下:干干巴巴,麻麻赖赖,一点都不圆润……
day03
内容
1. 自定义圆形进度条ui-RoundProgress
- 设置圆形进度条,并在中间设置文本,实时更新数据,并设置进度条动态效果,效果图如下:
- 自定义进度条详细步骤,以及==自定义属性的使用== 在 : 自定义View_圆形进度条&自定义属性的定义和使用.
2. 自定义 ui-MyScrollView,实现头尾部的下拉、上拉
-
创建 MyScrollView.java 继承自 ScrollView ;实现,划到头还可以继续滑,划到尾也可以继续划;
-
不需要重写 onLayout();因为不是在 Scroll 布局上下添加布局,而是,在触摸事件时,对其进行一个重新布局;
-
当 ScrollView 滑动到边缘(头 或 尾时),或者自身动画结束时,才对其处理,否则按照 ScrollView 的默认操作 super();
-
滑动原理:手指滑动时,记下布局处于临界位置的坐标,将坐标记录在 new Rectf 中,并将原布局放置在,拖动后的高度;回弹时,设置动画将布局慢慢的回弹到矩形记录的原始坐标处;
@Override public boolean onTouchEvent(MotionEvent ev) { if (childView == null || !isFinishAnimation) { return super.onTouchEvent(ev); } int eventY = (int) ev.getY();//获取当前的y轴坐标 switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: lastY = eventY; break; case MotionEvent.ACTION_MOVE: int dy = eventY - lastY;//微小的移动量 if (isNeedMove()) { if (normal.isEmpty()) { //记录了childView的临界状态的左、上、右、下 normal.set(childView.getLeft(), childView.getTop(), childView.getRight(), childView.getBottom()); } //重新布局 childView.layout(childView.getLeft(), childView.getTop() + dy / 2, childView.getRight(), childView.getBottom() + dy / 2); } lastY = eventY;//重新赋值 break; case MotionEvent.ACTION_UP: if (isNeedAnimation()) { //使用平移动画 int translateY = childView.getBottom() - normal.bottom; TranslateAnimation translateAnimation = new TranslateAnimation(0, 0, 0, -translateY); translateAnimation.setDuration(200); translateAnimation.setFillAfter(true);//停留在最终位置上 translateAnimation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { isFinishAnimation = false; } @Override public void onAnimationEnd(Animation animation) { isFinishAnimation = true; childView.clearAnimation();//清除动画 //重新布局;避免视图动画的属性没有移过来; childView.layout(normal.left, normal.top, normal.right, normal.bottom); //清除normal的数据 normal.setEmpty(); } @Override public void onAnimationRepeat(Animation animation) { } }); childView.startAnimation(translateAnimation); } break; } return super.onTouchEvent(ev); } private boolean isNeedMove() { //获取子视图的高度 int childMeasuredHeight = childView.getMeasuredHeight(); //获取布局的高度 int scrollViewMeasuredHeight = this.getMeasuredHeight(); //dy >= 0 int dy = childMeasuredHeight - scrollViewMeasuredHeight; //获取用户在y轴方向上的偏移量 (上 + 下 -) int scrollY = this.getScrollY(); if (scrollY <= 0 || scrollY >= dy) { //按照我们自定义的MyScrollView的方式处理 return true; } //其他处在临界范围内的,返回false。即表示,仍按照ScrollView的方式处理 return false; }
-
事件冲突处理;这里当 down 在 ViewPager上时,滑动效果有异常情况;故这里在 onInterceptTouchEvent() 中,让父视图对子视图的事件进行拦截;
3. Fragment 的抽取
- 将 用到的 多个 Fragment 中公共部分抽取成单独的基类;
4. 联网加载数据的4种状态的抽取及代码优化
-
面临的问题;每一个 Fragment 都对应四种状态,
加载中;联网获取数据成功且有数据;联网获取数据失败、联网获取数据成功但数据为空;
对应的四种布局; -
如果每个 Fragment 都创建这四种状态,获得请求状态后再将多于页面隐藏,也不是不可以……
-
创建抽象类 LoadingPage.java
//提供4种不同的显示状态及当前的状态 private static final int PAGE_STATE_LOADING = 1; private static final int PAGE_STATE_ERROR = 2; private static final int PAGE_STATE_EMPTY = 3; private static final int PAGE_STATE_SUCCESS = 4; //当前的状态:默认是加载状态 private int page_state_current = 1; //2.提供4种不同的界面,并在第3步中初始化 private View loadingView; …… //3.1构造器的初始化方法中根据当前的状态,加载不同的界面显示 private void init() { params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT); if(loadingView == null){ loadingView = UIUtils.getXmlView(R.layout.page_loading); addView(loadingView, params); } if (errorView == null) { errorView = UIUtils.getXmlView(R.layout.page_error); addView(errorView, params); } if (emptyView == null) { emptyView = UIUtils.getXmlView(R.layout.page_empty); addView(emptyView, params); } //界面中显示 View 的操作:需要在主线程中执行 showSafePage(); } //3.2 showSafePage():保证内部的操作在主线程中执行。 private void showSafePage() { UIUtils.runOnUiThread(new Runnable() { @Override public void run() { showPage(); } }); } //3.3 在如下方法中确定显示的View private void showPage() { loadingView.setVisibility(page_state_current == PAGE_STATE_LOADING ? View.VISIBLE : View.GONE); errorView.setVisibility(page_state_current == PAGE_STATE_ERROR ? View.VISIBLE : View.GONE); emptyView.setVisibility(page_state_current == PAGE_STATE_EMPTY ? View.VISIBLE : View.GONE); if(successView == null){ successView = UIUtils.getXmlView(layoutId()); addView(successView,params); } successView.setVisibility(page_state_current == PAGE_STATE_SUCCESS ? View.VISIBLE : View.GONE); } //4.LadingPage中要显示哪个View,取决于联网获取数据的情况。鉴于不同页面都有联网的需求,进而将联网操作声明在LoadingPage中。 //定义如下的方法,联网下载数据,并决定了当前的页面状态(加载、失败、空数据、成功) public void show(){ if(page_state_current != PAGE_STATE_LOADING){ page_state_current = PAGE_STATE_LOADING; } String url = url(); if(TextUtils.isEmpty(url)){ resultState = ResultState.SUCCESS; resultState.setContent(""); loadingPage(); }else{ client.get(url(),params(),new AsyncHttpResponseHandler(){...} } } //5.使用到的枚举类: public enum ResultState { ERROR(2), EMPTY(3), SUCCESS(4); private int state; ResultState(int state) { this.state = state; } private String content;//保存的内部数据 public String getContent() { return content; } public void setContent(String content) { this.content = content; } }
-
在 基类 中使用 LoadingPage;
-
此时 基类 的回调方法 onCreateView() 将 LoadingPage 作为返回值,实例化 LoadingPage ,并重写所有的抽象方法。
loadingPage = new LoadingPage(container.getContext()){ @Override public int layoutId() { return getLayoutId(); } @Override protected void onSuccess(ResultState resultState, View successView) { findViews(view_success); initTitle(); initData(resultState.getContent()); } @Override protected RequestParams params() { return getParams(); } @Override protected String url() { return getUrl(); } }; return loadingPage;
-
HomeFragment 等 Fragment 中重写所有 基类 中的抽象方法;
-
需要 BaseFragment 的 onActivityCreated() 中提供 LoadingPage 中联网操作的调用:
public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); loadingPage.show(); }
-
盲区
- 声明:本博客根据尚硅谷项目实战: 硅谷金融.学习整理;
- 对于画布绘制圆、矩形、圆弧以及确定文本的坐标有所疏忽,也就是有多回顾和收获;
- 对于为什么声明自定义属性的理解有了深入认识:让属性就定义在属性的 xml 中;对于如何定义并使用自定义属性有了认识!
- 对于代码的抽取,不熟悉,花费时间太长了;尤其是对于今天的 LoadingPage 的抽取,真是要了我老命,我现在还没整明白……
- handler.postDelayed() 失效?难道必须放在主线程?又是一个盲区!handler
彩蛋
-
菜单是我一个 BUG 改了一天的成果;
-
问题如下:
handler.postDelayed(new Runnable() { @Override public void run() { //中间的代码不执行! } }, 1000);
-
解决办法就是,拒绝延迟;
new Runnable() { @Override public void run() { } }.run();
-
那么问题来了,为什么 postDelayed() 里面的
Runnable() 不执行呢??? -
或者说, handler 使用,必须搁在主线程??
-
不知不觉有暴露出一个盲区嗷……
其他笔记
金融App
商城
Android项目实战—— 商城APP.
新闻
Android项目实战—— 新闻APP.