导图社区 中级Android开发教学方案总结
知识全面详细,干货满满,现在不收藏,还在等什么呢。
编辑于2022-11-02 10:17:54 广东中级Android开发教学方案总结
第六周
第28天(RecyclerView及CardView)
RecyclerView
1. 概要
1. RecyclerView是android-support-v7-21版本中新增的一个Widget,官方介绍RecyclerView 是 ListView 的升级版本,更加先进和灵活
2. 为什么要用使用RecyclerView?
1. RecyclerView控件和ListView的原理有很多相似的地方,都是维护少量的View来进行显示大量的数据,不过RecyclerView控件比ListView更加高级并且更加灵活。当我们的数据因为用户事件或者网络事件发生改变的时候也能很好的进行显示。和ListView不同的是,RecyclerView不用在负责Item的显示相关的功能,在这边所有有关布局,绘制,数据绑定等都被分拆成不同的类进行管理
2. 整体上看RecyclerView架构,高度的解耦,异常的灵活,通过设置它提供的不同LayoutManager,ItemDecoration , ItemAnimator实现不同的效果
2. 使用
1. 基础
1.1. 第一步 初始化RecyclerView
1.1.1. recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
1.2. 第二步 初始化布局管理器
1.2.1. LinearLayoutManager layoutManager = new LinearLayoutManager(this );
1.2.1.1. RecyclerView.LayoutManager是一个抽象类,系统为我们提供了三个实现类
1.2.1.2. LinearLayoutManager即线性布局,
1.2.1.3. GridLayoutManager即表格布局
1.2.1.4. StaggeredGridLayoutManager即流式布局,如瀑布流效果
1.3. 第三步 设置布局管理器
1.3.1. recyclerView.setLayoutManager(layoutManager);
1.4. 第四步 初始化适配器
1.4.1. RecyclerView的Adapter与ListView的Adapter还是有点区别的
1.4.2. 第一步 写一个类继承RecyclerView.Adapter
1.4.2.1. public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyHolder> {}
1.4.3. 第二步 写一个类继承ViewHolder
1.4.3.1. public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyHolder> { public class MyHolder extends RecyclerView.ViewHolder { public MyHolder(View itemView) { super(itemView); } } }
1.4.4. 第三步实现Adapter抽象类的三个方法
1.4.4.1. onCreateViewHolder()
这个方法主要生成为每个Item inflater出一个View,但是该方法返回的是一个ViewHolder。该方法把View直接封装在ViewHolder中,然后我们面向的是ViewHolder这个实例,当然这个ViewHolder需要我们自己去编写。直接省去了当初的convertView.setTag(holder)和convertView.getTag()这些繁琐的步骤。
1.4.4.2. onBindViewHolder()
这个方法主要用于适配渲染数据到View中。方法提供给你了一个viewHolder,而不是原来的convertView。
1.4.4.3. getItemCount()
这个方法就类似于BaseAdapter的getCount方法了,即总共有多少个条目。
1.5. 第五步 绑定适配器
1.5.1. recyclerView.setAdapter( recycleAdapter);
2. 进阶
1. 如何添加分割线
1. 概要
1.1. 可以通过RecyclerView.addItemDecoration(ItemDecoration decoration)这个方法进行设置,其中它需要的参数就是我们自己定义的继承自ItemDecoration的一个对象。我们可以创建一个继承RecyclerView.ItemDecoration类来绘制分隔线,通过ItemDecoration可以让我们每一个Item从视觉上面相互分开来,例如ListView的divider非常相似的效果。当然像我们上面的例子ItemDecoration我们没有设置也没有报错,那说明ItemDecoration我们并不是强制需要使用,作为我们开发者可以设置或者不设置Decoration的。实现一个ItemDecoration,系统提供的ItemDecoration是一个抽象类
2. 核心代码
2.1. public static abstract class ItemDecoration { public void onDraw(Canvas c,RecyclerView parent,State state) { onDraw(c,parent); } public void onDrawOver(Canvas c,RecyclerView parent,State state) { onDrawOver(c,parent); } public void getItemOffsets(RectoutRect, View view,RecyclerView parent,State state) { getItemOffsets(outRect,((LayoutParams)view.getLayoutParams()).getViewLayoutPosition(),parent); } }
3. ListView效果
public class DividerItemDecoration extends RecyclerView.ItemDecoration { private static final int[] ATTRS = new int[]{ android.R.attr.listDivider }; public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL; public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL; private Drawable mDivider; private int mOrientation; public DividerItemDecoration(Context context, int orientation) { final TypedArray a = context.obtainStyledAttributes(ATTRS); mDivider = a.getDrawable(0); a.recycle(); setOrientation(orientation); } public void setOrientation(int orientation) { if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) { throw new IllegalArgumentException("invalid orientation"); } mOrientation = orientation; } @Override public void onDraw(Canvas c, RecyclerView parent) { Log.v("recyclerview - itemdecoration", "onDraw()"); if (mOrientation == VERTICAL_LIST) { drawVertical(c, parent); } else { drawHorizontal(c, parent); } } public void drawVertical(Canvas c, RecyclerView parent) { final int left = parent.getPaddingLeft(); final int right = parent.getWidth() - parent.getPaddingRight(); final int childCount = parent.getChildCount(); for (int i = 0; i final View child = parent.getChildAt(i); android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext()); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams(); final int top = child.getBottom() + params.bottomMargin; final int bottom = top + mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } public void drawHorizontal(Canvas c, RecyclerView parent) { final int top = parent.getPaddingTop(); final int bottom = parent.getHeight() - parent.getPaddingBottom(); final int childCount = parent.getChildCount(); for (int i = 0; i final View child = parent.getChildAt(i); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams(); final int left = child.getRight() + params.rightMargin; final int right = left + mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } @Override public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { if (mOrientation == VERTICAL_LIST) { outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); } else { outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); } } }
4. GridView效果
public class DividerGridItemDecoration extends RecyclerView.ItemDecoration { private static final int[] ATTRS = new int[] { android.R.attr.listDivider }; private Drawable mDivider; public DividerGridItemDecoration(Context context) { final TypedArray a = context.obtainStyledAttributes(ATTRS); mDivider = a.getDrawable(0); a.recycle(); } @Override public void onDraw(Canvas c, RecyclerView parent, State state) { drawHorizontal(c, parent); drawVertical(c, parent); } private int getSpanCount(RecyclerView parent) { // 列数 int spanCount = -1; LayoutManager layoutManager = parent.getLayoutManager(); if (layoutManager instanceof GridLayoutManager) { spanCount = ((GridLayoutManager) layoutManager).getSpanCount(); } else if (layoutManager instanceof StaggeredGridLayoutManager) { spanCount = ((StaggeredGridLayoutManager) layoutManager) .getSpanCount(); } return spanCount; } public void drawHorizontal(Canvas c, RecyclerView parent) { int childCount = parent.getChildCount(); for (int i = 0; i { final View child = parent.getChildAt(i); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams(); final int left = child.getLeft() - params.leftMargin; final int right = child.getRight() + params.rightMargin + mDivider.getIntrinsicWidth(); final int top = child.getBottom() + params.bottomMargin; final int bottom = top + mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } public void drawVertical(Canvas c, RecyclerView parent) { final int childCount = parent.getChildCount(); for (int i = 0; i { final View child = parent.getChildAt(i); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams(); final int top = child.getTop() - params.topMargin; final int bottom = child.getBottom() + params.bottomMargin; final int left = child.getRight() + params.rightMargin; final int right = left + mDivider.getIntrinsicWidth(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } private boolean isLastColum(RecyclerView parent, int pos, int spanCount, int childCount) { LayoutManager layoutManager = parent.getLayoutManager(); if (layoutManager instanceof GridLayoutManager) { if ((pos + 1) % spanCount == 0)// 如果是最后一列,则不需要绘制右边 { return true; } } else if (layoutManager instanceof StaggeredGridLayoutManager) { int orientation = ((StaggeredGridLayoutManager) layoutManager) .getOrientation(); if (orientation == StaggeredGridLayoutManager.VERTICAL) { if ((pos + 1) % spanCount == 0)// 如果是最后一列,则不需要绘制右边 { return true; } } else { childCount = childCount - childCount % spanCount; if (pos >= childCount)// 如果是最后一列,则不需要绘制右边 return true; } } return false; } private boolean isLastRaw(RecyclerView parent, int pos, int spanCount, int childCount) { LayoutManager layoutManager = parent.getLayoutManager(); if (layoutManager instanceof GridLayoutManager) { childCount = childCount - childCount % spanCount; if (pos >= childCount)// 如果是最后一行,则不需要绘制底部 return true; } else if (layoutManager instanceof StaggeredGridLayoutManager) { int orientation = ((StaggeredGridLayoutManager) layoutManager) .getOrientation(); // StaggeredGridLayoutManager 且纵向滚动 if (orientation == StaggeredGridLayoutManager.VERTICAL) { childCount = childCount - childCount % spanCount; // 如果是最后一行,则不需要绘制底部 if (pos >= childCount) return true; } else // StaggeredGridLayoutManager 且横向滚动 { // 如果是最后一行,则不需要绘制底部 if ((pos + 1) % spanCount == 0) { return true; } } } return false; } @Override public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { int spanCount = getSpanCount(parent); int childCount = parent.getAdapter().getItemCount(); if (isLastRaw(parent, itemPosition, spanCount, childCount))// 如果是最后一行,则不需要绘制底部 { outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); } else if (isLastColum(parent, itemPosition, spanCount, childCount))// 如果是最后一列,则不需要绘制右边 { outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); } else { outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight()); } } }
5. 设置
1. recyclerView .addItemDecoration(new DividerItemDecoration(this ))
2. recyclerView .addItemDecoration(new DividerGridItemDecoration(this ))
6. 修改样式
6.1.
2. 如何添加RecyclerView增加和删除的动画
1. 概要
1.1. RecyclerView增加和删除的动画是通过ItemAnimator这个类来实现的,ItemAnimator这类也是个抽象的类,系统默认给我们提供了一种增加和删除的动画
2. 设置
2.1. recyclerView.setItemAnimator( new DefaultItemAnimator());
3. 第三方库
1. 使用
1. 导包
1. RecyclerView 23.0.1或以下版本
1.1. dependencies { // jCenter compile 'jp.wasabeef:recyclerview-animators:1.3.0' }
2. RecyclerView 23.1.0 ( 2015-10) 以上版本
2.1. dependencies { // jCenter compile 'jp.wasabeef:recyclerview-animators:2.2.3' }
2. 第一步
1. RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
2. recyclerView.setItemAnimator(new SlideInLeftAnimator());
3. RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
4. recyclerView.setItemAnimator(new SlideInUpAnimator(new OvershootInterpolator(1f));
3. 第二步
3.1. public void remove(int position) { mDataSet.remove(position); notifyItemRemoved(position); } public void add(String text, int position) { mDataSet.add(position, text); notifyItemInserted(position); }
4. 第三步改变动画时间
1. recyclerView.getItemAnimator().setAddDuration(1000);
2. recyclerView.getItemAnimator().setRemoveDuration(1000);
3. recyclerView.getItemAnimator().setMoveDuration(1000);
4. recyclerView.getItemAnimator().setChangeDuration(1000);
5. 第四步
1. SlideInLeftAnimator animator = new SlideInLeftAnimato();
2. animator.setInterpolator(new OvershootInterpolator());
3. recyclerView.setItemAnimator(animator);
2. Github地址:https://github.com/wasabeef/recyclerview-animators
3. RecyclerView.Adapter中刷新的几个方法的对比
1. notifyDataSetChanged()这个方法跟我们平时用到的ListView的Adapter的方法一样,这里就不多做描述了。
2. notifyItemChanged(int position),当position位置的数据发生了改变时就会调用这个方法,就会回调对应position的onBindViewHolder()方法了,当然,因为ViewHolder是复用的,所以如果position在当前屏幕以外,也就不会回调了,因为没有意义,下次position滚动会当前屏幕以内的时候同样会调用onBindViewHolder()方法刷新数据了。其他的方法也是同样的道理。public final void notifyItemRangeChanged(int positionStart, int itemCount),顾名思义,可以刷新从positionStart开始itemCount数量的item了(这里的刷新指回调onBindViewHolder()方法)。
3. public final void notifyItemInserted(int position),这个方法是在第position位置被插入了一条数据的时候可以使用这个方法刷新,注意这个方法调用后会有插入的动画,这个动画可以使用默认的,也可以自己定义。
4. public final void notifyItemMoved(int fromPosition, int toPosition),这个方法是从fromPosition移动到toPosition为止的时候可以使用这个方法刷新
5. public final void notifyItemRangeInserted(int positionStart, int itemCount),显然是批量添加。
6. public final void notifyItemRemoved(int position),第position个被删除的时候刷新,同样会有动画。
7. public final void notifyItemRangeRemoved(int positionStart, int itemCount),批量删除。
4. 给RecyclerView增加条目点击事件
3. 实战案例
1. 根据不同的类型加载不同的item
2. 添加头布局和底布局
3. 实现下拉刷新上拉加载更多
4. Adapter封装
3. 重要类
1. RecyclerView.Adapter 托管数据集合,为每个Item创建视图
2. RecyclerView.ViewHolder 保存用于显示每个数据条目的子View
3. RecyclerView.LayoutManager
1. 作用:将每个条目的视图放置于适当的位置
2. LinearLayoutManager(子类)
1. 显示在垂直或水平滚动列表项
3. StaggeredGridLayoutManager(子类)
1. 显示了交错网格项目(瀑布流)
2. private void getRandomHeight(List<String> lists){//得到随机item的高度 heights = new ArrayList<>(); for (int i = 0; i < lists.size(); i++) { heights.add((int)(200+Math.random()*400)); } }
3. ViewGroup.LayoutParams params = holder.itemView.getLayoutParams();//得到item的LayoutParams布局参数 params.height = heights.get(position);//把随机的高度赋予item布局 holder.itemView.setLayoutParams(params);//把params设置给item布局
4. GridLayoutManager(LinearLayoutManager的子类)
1. 显示在网格中的项目
4. RecyclerView.ItemDecoration 负责item之间的分割效果
public class DividerItemDecoration extends RecyclerView.ItemDecoration { private static final int[] ATTRS = new int[]{ android.R.attr.listDivider }; public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL; public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL; private Drawable mDivider; private int mOrientation; public DividerItemDecoration(Context context, int orientation) { final TypedArray a = context.obtainStyledAttributes(ATTRS); mDivider = a.getDrawable(0); a.recycle(); setOrientation(orientation); } public void setOrientation(int orientation) { if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) { throw new IllegalArgumentException("invalid orientation"); } mOrientation = orientation; } @Override public void onDraw(Canvas c, RecyclerView parent) { Log.v("recyclerview - itemdecoration", "onDraw()"); if (mOrientation == VERTICAL_LIST) { drawVertical(c, parent); } else { drawHorizontal(c, parent); } } public void drawVertical(Canvas c, RecyclerView parent) { final int left = parent.getPaddingLeft(); final int right = parent.getWidth() - parent.getPaddingRight(); final int childCount = parent.getChildCount(); for (int i = 0; i final View child = parent.getChildAt(i); android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext()); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams(); final int top = child.getBottom() + params.bottomMargin; final int bottom = top + mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } public void drawHorizontal(Canvas c, RecyclerView parent) { final int top = parent.getPaddingTop(); final int bottom = parent.getHeight() - parent.getPaddingBottom(); final int childCount = parent.getChildCount(); for (int i = 0; i final View child = parent.getChildAt(i); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams(); final int left = child.getRight() + params.rightMargin; final int right = left + mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } @Override public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { if (mOrientation == VERTICAL_LIST) { outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); } else { outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); } } }
1. 在主题中修改
public class DividerGridItemDecoration extends RecyclerView.ItemDecoration { private static final int[] ATTRS = new int[] { android.R.attr.listDivider }; private Drawable mDivider; public DividerGridItemDecoration(Context context) { final TypedArray a = context.obtainStyledAttributes(ATTRS); mDivider = a.getDrawable(0); a.recycle(); } @Override public void onDraw(Canvas c, RecyclerView parent, State state) { drawHorizontal(c, parent); drawVertical(c, parent); } private int getSpanCount(RecyclerView parent) { // 列数 int spanCount = -1; LayoutManager layoutManager = parent.getLayoutManager(); if (layoutManager instanceof GridLayoutManager) { spanCount = ((GridLayoutManager) layoutManager).getSpanCount(); } else if (layoutManager instanceof StaggeredGridLayoutManager) { spanCount = ((StaggeredGridLayoutManager) layoutManager) .getSpanCount(); } return spanCount; } public void drawHorizontal(Canvas c, RecyclerView parent) { int childCount = parent.getChildCount(); for (int i = 0; i { final View child = parent.getChildAt(i); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams(); final int left = child.getLeft() - params.leftMargin; final int right = child.getRight() + params.rightMargin + mDivider.getIntrinsicWidth(); final int top = child.getBottom() + params.bottomMargin; final int bottom = top + mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } public void drawVertical(Canvas c, RecyclerView parent) { final int childCount = parent.getChildCount(); for (int i = 0; i { final View child = parent.getChildAt(i); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams(); final int top = child.getTop() - params.topMargin; final int bottom = child.getBottom() + params.bottomMargin; final int left = child.getRight() + params.rightMargin; final int right = left + mDivider.getIntrinsicWidth(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } private boolean isLastColum(RecyclerView parent, int pos, int spanCount, int childCount) { LayoutManager layoutManager = parent.getLayoutManager(); if (layoutManager instanceof GridLayoutManager) { if ((pos + 1) % spanCount == 0)// 如果是最后一列,则不需要绘制右边 { return true; } } else if (layoutManager instanceof StaggeredGridLayoutManager) { int orientation = ((StaggeredGridLayoutManager) layoutManager) .getOrientation(); if (orientation == StaggeredGridLayoutManager.VERTICAL) { if ((pos + 1) % spanCount == 0)// 如果是最后一列,则不需要绘制右边 { return true; } } else { childCount = childCount - childCount % spanCount; if (pos >= childCount)// 如果是最后一列,则不需要绘制右边 return true; } } return false; } private boolean isLastRaw(RecyclerView parent, int pos, int spanCount, int childCount) { LayoutManager layoutManager = parent.getLayoutManager(); if (layoutManager instanceof GridLayoutManager) { childCount = childCount - childCount % spanCount; if (pos >= childCount)// 如果是最后一行,则不需要绘制底部 return true; } else if (layoutManager instanceof StaggeredGridLayoutManager) { int orientation = ((StaggeredGridLayoutManager) layoutManager) .getOrientation(); // StaggeredGridLayoutManager 且纵向滚动 if (orientation == StaggeredGridLayoutManager.VERTICAL) { childCount = childCount - childCount % spanCount; // 如果是最后一行,则不需要绘制底部 if (pos >= childCount) return true; } else // StaggeredGridLayoutManager 且横向滚动 { // 如果是最后一行,则不需要绘制底部 if ((pos + 1) % spanCount == 0) { return true; } } } return false; } @Override public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { int spanCount = getSpanCount(parent); int childCount = parent.getAdapter().getItemCount(); if (isLastRaw(parent, itemPosition, spanCount, childCount))// 如果是最后一行,则不需要绘制底部 { outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); } else if (isLastColum(parent, itemPosition, spanCount, childCount))// 如果是最后一列,则不需要绘制右边 { outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); } else { outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight()); } } }
1.1.
5. RecyclerView.ItemAimator 在条目被添加、移除或者重排序时添加动画效果
1. DefaultItemAnimator 唯一默认实现类 也可继承ViewPropertyAnimator实现自己想要的效果
2. 第三方
2.1. https://github.com/wasabeef/recyclerview-animators
2.2. 导包
1. RecyclerView 23.0.1或以下版本
1.1. dependencies { // jCenter compile 'jp.wasabeef:recyclerview-animators:1.3.0' }
2. RecyclerView 23.1.0 ( 2015-10) 以上版本
2.1. compile 'jp.wasabeef:recyclerview-animators:2.1.0'
3. 使用
3.1. 第一步
1. RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
2. recyclerView.setItemAnimator(new SlideInLeftAnimator());
3. RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
4. recyclerView.setItemAnimator(new SlideInUpAnimator(new OvershootInterpolator(1f));
3.2. 第二步
1. public void remove(int position) { mDataSet.remove(position); notifyItemRemoved(position); } public void add(String text, int position) { mDataSet.add(position, text); notifyItemInserted(position); }
3.3. 第三步改变动画时间
1. recyclerView.getItemAnimator().setAddDuration(1000);
2. recyclerView.getItemAnimator().setRemoveDuration(1000);
3. recyclerView.getItemAnimator().setMoveDuration(1000);
4. recyclerView.getItemAnimator().setChangeDuration(1000);
3.4. 第四步
1. SlideInLeftAnimator animator = new SlideInLeftAnimato();
2. animator.setInterpolator(new OvershootInterpolator());
3. recyclerView.setItemAnimator(animator);
4. 生命周期
1. 一个RecyclerView的Item加载是有顺序的,类似于Activity的生命周期(姑且这么叫把),具体可以对adapter的每个方法进行重写打下日志进行查看,具体大致为:
2. getItemViewType(获取显示类型,返回值可在onCreateViewHolder中拿到,以决定加载哪种ViewHolder)
3. onCreateViewHolder(加载ViewHolder的布局)
4. onViewAttachedToWindow(当Item进入这个页面的时候调用)
5. onBindViewHolder(将数据绑定到布局上,以及一些逻辑的控制就写这啦)
6. onViewDetachedFromWindow(当Item离开这个页面的时候调用)
7. onViewRecycled(当Item被回收的时候调用)
5. 推荐的第三方开源库
5.1. https://github.com/WuXiaolong/PullLoadMoreRecyclerView
5.2. https://github.com/dkmeteor/Android-PullToRefresh-RecyclerView-Extention
5.3. https://github.com/jianghejie/XRecyclerView
5.4. https://github.com/cymcsg/UltimateRecyclerView
5.5. https://github.com/cundong/HeaderAndFooterRecyclerView
6. 与ListView的区别
1. ViewHolder
1.1. ListView中,ViewHolder需要自己来定义,且这只是一种推荐的使用方式,不使用当然也可以,这不是必须的
1.2. 而在RecyclerView中使用 RecyclerView.ViewHolder 则变成了必须
2. LayoutManager
2.1. ListView只能在垂直方向上滚动
2.2. RecyclerView相较于ListView,在滚动上面的功能扩展了许多,它可以支持多种类型列表的展示要求
2.2.1. LinearLayoutManager ,可以支持水平和竖直方向上滚动的列表。
2.2.2. StaggeredGridLayoutManager ,可以支持交叉网格风格的列表,类似于瀑布流或者Pinterest。
2.2.3. GridLayoutManager ,支持网格展示,可以水平或者竖直滚动,如展示图片的画廊
3. Adapter
3.1. ListView的Adapter中,getView是最重要的方法,它将视图跟position绑定起来,同时我们也能够通过registerDataObserver在Adapter中注册一个观察者来监听数据变化
3.2. RecyclerView的Adapter 一般继承RecyclerView.Adapter 的实现的,我们必须采取措施将数据提供给Adapter
4. 分割线
4.1. 在ListView中如果我们想要在item之间添加间隔符,我们只需要在布局文件中对ListView添加如下属性即可:
4.2. RecyclerView必须通过继承ItemDecoration类来实现
5. item点击事件
5.1. ListView通过AdapterView.OnItemClickListener接口来监听点击事件
5.2. RecyclerView则通过RecyclerView.OnItemTouchListener接口来监听事件。它虽然增加了实现的难度,但是却给予开发人员拦截触摸事件更多的控制权限
CardView
简介
CardView如Linearlayout、Framelayout一样都是ViewGroup,即其他控件的容器。CardView继承于Framelayout,所以Framelayout的属性他都有,同时CardView还有几个特殊的属性
使用
导包
dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') compile 'com.android.support:cardview-v7:23.2.1' }
代码
属性
app:cardBackgroundColor 设置背景色
app:cardCornerRadius 设置圆角大小
app:cardElevation 设置z轴阴影
app:cardMaxElevation 设置z轴最大高度值
app:cardUseCompatPadding 是否使用CompadPadding
app:cardPreventCornerOverlap 是否使用PreventCornerOverlap
app:contentPadding 内容的padding
app:contentPaddingLeft 内容的左padding
app:contentPaddingTop 内容的上padding
app:contentPaddingRight 内容的右padding
app:contentPaddingBottom 内容的底padding
开发注意:
注意不同 SDK 版本(低于 Lollipop 21)上的边距(Margin)效果
Google 在 Android Lollipop 中引入了 Material Design 设计中的阴影(Elevation)和 Z 轴位移,其目的就是突出界面中不同元素之间的层次关系。为了统一不同系统版本的视觉效然而,在低版本中设置了 CardElevation 之后 CardView 会自动留出空间供阴影显示,而 Lollipop 之后则需要手动设置 Margin 边距来预留空间,导致我在设置 Margin 在 Android 5.x 机器上调试好后,在 Kitkat 机器调试时发现边距非常大,严重地浪费了屏幕控Google 针对 SDK 21 以下的系统给 CardView 加入一个 Elevation 兼容(即 XML 中的 app:cardElevation 和 Java 代码中的 setCardElevation)。因此,我们需要自定义一个 dimen 作为 CardView 的 Margin 值:
创建 /res/value 和 /res/value-v21 资源文件夹于项目对应 Module 目录下,前者放置旧版本/通用的资源文件(了解的可以跳过),后者放置 21 及更高 SDK 版本的资源文件。
在 value 内的 dimen.xml 创建一个 Dimension (<dimen> 属性),随便命个名(如 xxx_card_margin)并填入数值 0dp。
接着在 value-v21 文件夹内的 dimen.xml 创建名字相同的 Dimension,并填入你期望的预留边距(一般和 CardElevation 阴影大小相同)
最后,在你布局中的 CardView 中设置 android:layout_margin="@dimen/xxx_card_margin"
Card 添加点击效果
当使用 CardView 的场合是作为列表中的一个 Item 且直接单击 Item 有相应的操作,那么就有必要加上视觉反馈来告诉用户这个 Card 是可点击的。
Android FrameLayout的android:foreground属性可以设置单击时的前景色
你可以直接给 CardView 加上
android:foreground="?attr/selectableItemBackground" 这个属性会在 Lollipop 上自动加上 Ripple
第29天(Refresh)
传统动画风格
XRecyclerView
https://github.com/jianghejie/XRecyclerView
使用
compile 'com.jcodecraeer:xrecyclerview:1.2.7'
导包
compile 'com.jcodecraeer:xrecyclerview:1.2.7'
设置下拉刷新上拉加载更多
mRecyclerView.setLoadingListener(new XRecyclerView.LoadingListener() { @Override public void onRefresh() { //refresh data here } @Override public void onLoadMore() { // load more data here } });
设置刷新完成
mRecyclerView.loadMoreComplete();
mRecyclerView.refreshComplete();
设置刷新样式
mRecyclerView.setRefreshProgressStyle(int style);
mRecyclerView.setLaodingMoreProgressStyle(int style);
例如:mRecyclerView.setLaodingMoreProgressStyle(ProgressStyle.SquareSpin);
添加头部
mRecyclerView.addHeaderView(header);
LRecyclerView
https://github.com/jdsjlzx/LRecyclerView
Diaglog风格
PullLoadMoreRecyclerView
https://github.com/WuXiaolong/PullLoadMoreRecyclerView
BaseRecyclerViewAdapterHelper
https://github.com/CymChad/BaseRecyclerViewAdapterHelper
功能介绍
它可以大量减少你Adapter写的代码(和正常的Adapter相比至少三分之二的)
它可以很轻松的添加RecyclerView加载动画
添加item点击事件
新增添加头部、添加尾部
新增下拉刷新、上拉加载更多
新增分组
自定义item类型
使用
导包
先在Project下build.gradle 的 repositories
allprojects { repositories { ... maven { url "https://jitpack.io" } } }
然后在moudle中dependencies中添加
dependencies { compile 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.0.0' }
创建Adapter
public class QuickAdapter extends BaseQuickAdapter<Status> { public QuickAdapter(Context context) { super(context, R.layout.tweet, DataServer.getSampleData()); } @Override protected void convert(BaseViewHolder helper, Status item) { helper.setText(R.id.tweetName, item.getUserName()) .setText(R.id.tweetText, item.getText()) .setText(R.id.tweetDate, item.getCreatedAt()) .setImageUrl(R.id.tweetAvatar, item.getUserAvatar()) .setVisible(R.id.tweetRT, item.isRetweet()) .linkify(R.id.tweetText); } }
添加item点击事件
mQuickAdapter.setOnRecyclerViewItemClickListener(new BaseQuickAdapter.OnRecyclerViewItemClickListener() { @Override public void onItemClick(View view, int position) { //.. } });
添加动画
//默认为渐显效果 quickAdapter.openLoadAnimation();
// 默认提供5种方法(渐显、缩放、从下到上,从左到右、从右到左) quickAdapter.openLoadAnimation(BaseQuickAdapter.ALPHAIN);
// 自定义动画如此轻松 quickAdapter.openLoadAnimation(new BaseAnimation() { @Override public Animator[] getAnimators(View view) { return new Animator[]{ ObjectAnimator.ofFloat(view, "scaleY", 1, 1.1f, 1), ObjectAnimator.ofFloat(view, "scaleX", 1, 1.1f, 1) }; } });
添加头部添加尾部
mQuickAdapter.addHeaderView(getView());
mQuickAdapter.addFooterView(getView());
加载更多
mQuickAdapter.setOnLoadMoreListener(1, new BaseQuickAdapter.RequestLoadMoreListener() { @Override public void onLoadMoreRequested() { } });
自定义item类型
public class SectionAdapter extends BaseSectionQuickAdapter<MySection> { public SectionAdapter(Context context, int layoutResId, int sectionHeadResId, List data) { super(context, layoutResId, sectionHeadResId, data); } @Override protected void convert(BaseViewHolder helper, MySection item) { helper.setImageUrl(R.id.iv, (String) item.t); } @Override protected void convertHead(BaseViewHolder helper,final MySection item) { helper.setText(R.id.header, item.header); if(!item.isMroe)helper.setVisible(R.id.more,false); else helper.setOnClickListener(R.id.more, new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(context,item.header+"more..",Toast.LENGTH_LONG).show(); } }); }
UltimateRecyclerView
https://github.com/cymcsg/UltimateRecyclerView
SwipeRefreshLayout
下拉刷新控件 V4包下-android.support.v4.widget.SwipeRefreshLayout
常用方法
swipeRefreshLayout.setOnRefreshListener(this); //设置下拉刷新监听事件
swipeRefreshLayout.setColorSchemeColors(Color.RED, Color.BLUE, Color.GREEN);//设置进度条的颜色,可变数组
swipRefreshLayout.setRefreshing(true);
swipeRefreshLayout.setSize(SwipeRefreshLayout.LARGE); //设置圆形进度条大小
swipeRefreshLayout.setProgressBackgroundColorSchemeColor(Color.DKGRAY); //设置进度条背景颜色
swipeRefreshLayout.setDistanceToTriggerSync(80); //设置下拉多少距离之后开始刷新数据
swipeRefreshLayout.setColorSchemeColors() 设置进度条颜色,可设置多个值,进度条颜色在这多个颜色值之间变化
swipeRefreshLayout.setSize() 设置下拉出现的圆形进度条的大小,有两个值
SwipeRefreshLayout.DEFAULT
SwipeRefreshLayout.LARGE
swipeRefreshLayout.setProgressBackgroundColorSchemeColor()设置圆形进度条背景颜色。
swipeRefreshLayout.setDistanceToTriggerSync() 设置手势操作下拉多少距离之后开始刷新数据
第30天(机型适配)
1. 定义
1.1. 使得某一元素在Android不同尺寸、不同分辨率的手机上具备相同的显示效果
2. 重要概念
2.1. 屏幕尺寸
1. 定义:手机对角线的物理尺寸
2. 单位:英寸(inch),1英寸=2.54cm
3. Android手机常见的尺寸有5寸、5.5寸、6寸等等
2.2. 屏幕分辨率
1. 定义:手机在横向、纵向上的像素点数总和,一般描述成屏幕的"宽x高”=AxB
1.1. 屏幕在横向方向(宽度)上有A个像素点,在纵向方向(高)有B个像素点
1.2. 例子:1080x1920,即宽度方向上有1080个像素点,在高度方向上有1920个像素点
2. 单位:px(pixel),1px=1像素点(mdpi的时候)
2.1. 注意:UI设计师的设计图会以px作为统一的计量单位
3. Android手机常见的分辨率:320x480、480x800、720x1280、1080x1920
2.3. 屏幕像素密度
1. 定义:每英寸的像素点数
2. 单位:dpi或者ppi
2.4. 密度无关像素
1. 定义:density-independent pixel,叫dp或dip,与终端上的实际物理像素点无关
2. 单位:dp,可以保证在不同屏幕像素密度的设备上显示相同的效果
3. dp与px的转换
2.5. 独立比例像素
1. 定义:scale-independent pixel,叫sp或sip
2. 单位:sp
3. 文字大小的御用单位
2.6. 屏幕尺寸、分辨率、像素密度三者关系
1. 像素密度=√{(长度像素数^2+宽度像素数^2)}/ 屏幕尺寸
2. 例如:√(1920^2+1080^2)=2202.9 2202/5.2=423
3. 说明图
3.1.
4. 制图比例
4.1.
3. 为什么要进行Android屏幕适配
1. 由于Android系统的开放性,任何用户、开发者、OEM厂商、运营商都可以对Android进行定制
1. Android系统碎片化:小米定制的MIUI、魅族定制的flyme、华为定制的EMUI等等
2. Android机型屏幕尺寸碎片化:5寸、5.5寸、6寸等等
3. Android屏幕分辨率碎片化:320x480、480x800、720x1280、1080x1920
2. 当Android系统、屏幕尺寸、屏幕密度出现碎片化的时候,就很容易出现同一元素在不同手机上显示不同的问题
3. 为了保证用户获得一致的用户体验效果使得某一元素 在Android不同尺寸、不同分辨率的手机上具备相同的显示效果,于是,我们便需要对Android屏幕进行适配
4. 屏幕适配问题的本质
1. 使得布局、布局组件、图片资源、匹配不同的屏幕尺寸
1. 使得布局、布局组件自适应屏幕尺寸;
2. 根据屏幕的配置来加载相应的UI布局、用户界面流程
2. 使得图片资源匹配不同的屏幕密度
5. 解决方案
1. 布局适配
1. 使得布局元素自适应屏幕尺寸
1. 使用相对布局(RelativeLayout) 禁用绝对布局(AbsoluteLayout)
1. 由于绝对布局(AbsoluteLayout)适配性极差,所以极少使用
2. 对于线性布局(Linearlayout)、相对布局(RelativeLayout)和帧布局(FrameLayout)需要根据需求进行选择
2. 使用扩展包百分比(percent)
1. PercentRelativeLayout
1. layout_widthPercent 、 layout_heightPercent
2. layout_marginPercent
3. layout_marginLeftPercent
4. layout_marginTopPercent
5. layout_marginRightPercent
6. layout_marginBottomPercent
7. layout_marginStartPercent
8. layout_marginEndPercent
2. PercentFrameLayout
1. layout_widthPercent 、 layout_heightPercent
2. layout_marginPercent
3. layout_marginLeftPercent
4. layout_marginTopPercent
5. layout_marginRightPercent
6. layout_marginBottomPercent
7. layout_marginStartPercent
8. layout_marginEndPercent
2. 根据屏幕的配置来加载相应的UI布局
1. 虽然这些布局可以拉伸组件内外的空间以适应各种屏幕,但它们不一定能为每种屏幕都提供最佳的用户体验。因此,我们的应用不仅仅只实施灵活布局,还应该应针对各种屏幕配置提供一些备用布局
2. 应用场景:需要为不同屏幕尺寸的设备设计不同的布局
3. 使用限定符
1. 尺寸(size)限定符(API<3.2)
1.1. 尺寸的规格
1. small
2. normal
3. large
4. xlarge
1.2. 使用场景:当一款应用显示的内容较多,希望进行以下设置
1. 在平板电脑和电视的屏幕(>7英寸)上:实施“双面板”模式以同时显示更多内容
2. 在手机较小的屏幕上:使用单面板分别显示内容
1.3. 解决方案:你可以使用不同的layout尺寸来适配不同的屏幕大小
1. 例子 layout layout-large layout-small layout-xlarge
1.4. 但要注意的是,这种方式只适合Android 3.2版本之前。
2. 最小宽度(Smallest-width)限定符(API>3.2)
1. 定义:通过指定某个最小宽度(以 dp 为单位)来精确定位屏幕从而加载不同的UI资源
2. 使用场景:你需要为标准 7 英寸平板电脑匹配双面板布局(其最小宽度为 600 dp),在手机(较小的屏幕上)匹配单面板布局
3. 解决方案:您可以使用上文中所述的单面板和双面板这两种布局,但您应使用 sw600dp 指明双面板布局仅适用于最小宽度为 600 dp 的屏幕,而不是使用 large 尺寸限定符。
1. sw xxxdp,即small width的缩写,其不区分方向,即无论是宽度还是高度,只要大于 xxxdp,就采用次此布局
2. 例子:使用了layout-sw600dp的最小宽度限定符,即无论是宽度还是高度,只要大于600dp,就采用layout-sw600dp目录下的布局
3. 可用高度(h<N>dp)
3.1. 定义:指定资源应该使用的最小可用屏幕高度,以“dp”为单位,由 <N> 值定义。 在横向和纵向之间切换时,为了匹配当前实际高度,此配置值也会随之发生变化
3.2. 示例: h720dp h1024dp
4. 可用宽度(w<N>dp)
4.1. 定义:指定资源应该使用的最小可用屏幕宽度,以 dp 为单位,由 <N> 值定义。在横向和纵向之间切换时,为了匹配当前实际宽度,此配置值也会随之发生变化。
4.2. 示例 w720dp w1024dp
5. 布局别名
1. 应用场景:当你需要同时为Android 3.2版本前和Android 3.2版本后的手机进行屏幕尺寸适配的时候,由于尺寸限定符仅用于Android 3.2版本前,最小宽度限定符仅用于Android 3.2版本后,所以这会带来一个问题,为了很好地进行屏幕尺寸的适配,你需要同时维护layout-sw600dp和layout-large的两套main.xml平板布局,
1. 适配手机的单面板(默认)布局:res/layout/main.xml
2. 适配尺寸>7寸平板的双面板布局(Android 3.2前):res/layout-large/main.xml
3. 适配尺寸>7寸平板的双面板布局(Android 3.2后)res/layout-sw600dp/main.xml
6. 屏幕方向(Orientation)限定符
6.1. 使用场景:根据屏幕方向进行布局的调整
6.2. 方向规格
6.2.1. 小屏幕, 竖屏: 单面板
6.2.2. 小屏幕, 横屏: 单面板
6.2.3. 7 英寸平板电脑,纵向:单面板,带操作栏
6.2.4. 7 英寸平板电脑,横向:双面板,宽,带操作栏
6.2.5. 10 英寸平板电脑,纵向:双面板,窄,带操作栏
6.2.6. 10 英寸平板电脑,横向:双面板,宽,带操作栏
6.2.7. 电视,横向:双面板,宽,带操作栏
7. 官方文档
1. https://developer.android.com/guide/topics/resources/providing-resources.html(翻墙)
2. 布局控件适配
1. 使得布局组件自适应屏幕尺寸
2. 解决方案:使用"wrap_content"、"match_parent"和"weight“来控制视图组件的宽度和高度
3. 图片资源适配
1. 使得图片资源在不同屏幕密度上显示相同的像素效果
2. 具体做法:使用自动拉伸位图:Nine-Patch的图片类型
3. 使用场景:一个按钮的背景图片必须能够随着按钮大小的改变而改变。使用普通的图片将无法实现上述功能,因为运行时会均匀地拉伸或压缩你的图片
4. 解决方案:使用自动拉伸位图(nine-patch图片),后缀名是.9.png,它是一种被特殊处理过的PNG图片,设计时可以指定图片的拉伸区域和非拉伸区域;使用时,系统就会根据控件的大小自动地拉伸你想要拉伸的部分
1. 必须要使用.9.png后缀名,因为系统就是根据这个来区别nine-patch图片和普通的PNG图片的;
2. 当你需要在一个控件中使用nine-patch图片时,如android:background="@drawable/xxx"系统就会根据控件的大小自动地拉伸你想要拉伸的部分
4. 用户流程适配
1. 根据屏幕的配置来加载相应的用户界面流程
2. 使用场景:我们会根据设备特点显示恰当的布局,但是这样做,会使得用户界面流程可能会有所不同
2.1. 例如,如果应用处于双面板模式下,点击左侧面板上的项即可直接在右侧面板上显示相关内容;而如果该应用处于单面板模式下,点击相关的内容应该跳转到另外一个Activity进行后续的处理。
3. 步骤
1. 确定当前布局
1.1. 由于每种布局的实施都会稍有不同,因此我们需要先确定当前向用户显示的布局。例如,我们可以先了解用户所处的是“单面板”模式还是“双面板”模式。要做到这一点,可以通过查询指定视图是否存在以及是否已显示出来
2. 根据当前布局做出响应
3. 重复使用其他活动中的片段
4. 处理屏幕配置变化
第26天(自定义View)
1. setContentView
1. 结构图
1.1.
2. 概叙
1. 一个Activity包含有一个PhoneWIndow对象,二所有的ui部件都放在这个Phone中
2. ViewRoot这个类在android的UI结构中扮演的是一个中间者的角色,连接的是PhoneWindow跟WindowMangerService
3. WindowMangerService它是读取Android系统里所有事件,键盘事件,轨迹球事件等等,他怎么分发给Activity的呢,就是通过这个ViewRoot
4. 在每个PhoneWindow创建的时候,系统都会向WindowManager中的一个保存View的数组增加PhoneWindow的DecorView对象.WindowManager在保存好这个View对象的同时,也会新创建ViewRoot对象来沟通WindowService
3. Activity
1. 基本的页面单元,Activity包含了一个Window ,window上可以绘制各种View
2. Activity主要是用来做控制的,它可以选择要显示的View,也可以从View重获取数据然后把数据传递个Model层进行处理,然后用来显示出处理结果
4. PhoneWindow
4.1. 该类继承Window类,同时PhoneWindow 内部包含了一个DecorVIew的对象,简而言之,PhoneWindow是把一个FrameLayout进行了一定的包装,提供了一组通用的窗口操作接口,是Activity和整个View系统交互的接口
5. DecorView
5.1. 是Window中View的RootView,设置窗口属性,该类是一个FrameLayout的子类,并且是PhoneWindow中的一个内部类,Docoration翻译成中文就是修饰的意思,DecorView就是对普通FrameLayout进行了一定的修饰,比如添加通用的TitleBar,并且响应特定的案件消息等
6. ViewRoot
1. 并不是一个View类型,而是一个Handler
2. 主要作用
1. 向DecorView分发收到的用户发起Event事件,如按键,触屏等等
2. 与WindowManagerService交互,完成整个Activity的GUI的绘制
7. 基本流程
1. 构建mDecor对象,mDecor就是整个窗口的顶层视图,他主要包含了Title和ContentView两个区域,title区域就是我们的标题栏,例如ActionBar,ContentView区域就是 显示我们的xml布局内容的区域
2. 设置一些关于窗口的属性,初始化标题栏区域的内容显示区域
3. 加载顶层布局文件,转化为VIew,讲其添加到mDecor中
4. 获取内容容器的ContentParent是跟ViewGroup,由于ContentParent组织,管理其子视图,从而构建整个视图树,当Activity启动的时,就将这些内容显示在手机上
4.1.
2. 基础知识
1. 定义
1.1. 实现一个继承View或者View的子类的类叫自定义View
2. 应用场景
2.1. Android的framework有大量的Views用来与用户进行交互并显示不同种类的数据。但是有时候你的程序有个特殊的需求,而Android内置的views组件并不能实现(股票的实时统计图、电子书,圆形进度条等)
3. ViewGroup的作用
3.1. ViewGroup的子类则负责这些View类对象的布局,即规定了这些View放在屏幕的那些地方
4. View跟ViewGroup的关系
4.1. Android中的视图树是按着如下结构来组织的,即最顶层的是一个ViewGroup,View 只有ViewGroup下能够包含子节点,View则是叶子节点
4.1.1.
5. 坐标系
1. 屏幕坐标系与数学坐标系区别
1.1.
1.2.
1.3.
2. View的坐标系
1. getTop(); //获取子View左上角距父View顶部的距离
2. getLeft(); //获取子View左上角距父View左侧的距离
3. getBottom(); //获取子View右下角距父View顶部的距离
4. getRight(); //获取子View右下角距父View左侧的距离
5. 结构图
5.1.
3. MotionEvent中 get 和 getRaw 的区别
1. event.getX(), event.getY(); //触摸点相对于其所在组件坐标系的坐标
2. event.getRawY(),event.getRawX(); //触摸点相对于屏幕默认坐标系的坐标
3. 结构图
3.1.
6. 颜色介绍
1. 安卓支持的颜色模式
1. ARGB8888四通道高精度(32位)
2. ARGB4444四通道低精度(24位)
3. RGB565 屏幕默认模式(16位)
4. Alpha8 仅有透明通道(8位)
2. 颜色定义
1. 其中 A R G B 的取值范围均为0~255(即16进制的0x00~0xff) A 从ox00到oxff表示从透明到不透明。 RGB 从0x00到0xff表示颜色从浅到深。 当RGB全取最小值(0或0x000000)时颜色为黑色,全取最大值(255或0xffffff)时颜色为白色
2.
3. 几种创建或使用颜色的方式
1. java中定义颜色
2. 在xml文件中定义颜色
3. 在java文件中引用xml中定义的颜色
4. 在xml文件(layout或style)中引用或者创建颜色
4. 颜色混合模式(Alpha通道相关)
1. 通过前面介绍我们知道颜色一般都是四个通道(ARGB)的,其中(RGB)控制的是颜色,而A(Alpha)控制的是透明度。 因为我们的显示屏是没法透明的,因此最终显示在屏幕上的颜色里可以认为没有Alpha通道。Alpha通道主要在两个图像混合的时候生效。 默认情况下,当一个颜色绘制到Canvas上时的混合模式是这样计算的: (RGB通道) 最终颜色 = 绘制的颜色 + (1 - 绘制颜色的透明度) × Canvas上的原有颜色。
2. 注意
1. 这里我们一般把每个通道的取值从0(ox00)到255(0xff)映射到0到1的浮点数表示。
2. 这里等式右边的“绘制的颜色"、“Canvas上的原有颜色”都是经过预乘了自己的Alpha通道的值。如绘制颜色:0x88ffffff,那么参与运算时的每个颜色通道的值不是1.0,而是(1.0 * 0.5333 = 0.5333)。 (其中0.5333 = 0x88/0xff)使用这种方式的混合,就会造成后绘制的内容以半透明的方式叠在上面的视觉效果
5. 取色工具自行google
3. 分类
1. View
1. 一般继承自View,SurfaceView或其他的View,不包含子View
2. 步骤
1. 第一步:创建一个类继承View或者View的子类
2. 第二步:重写构造方法(初始化一些必要的属性)
1. 主要作用可以用于初始化一些的内容,和获取自定义属性
2. 构造方法
1. public void SloopView(Context context) {}
2. public void SloopView(Context context, AttributeSet attrs) {}
2.1. Constructor that is called when inflating a view from XML. This is called when a view is being constructed from an XML file, supplying attributes that were specified in the XML file. This version uses a default style of 0, so the only attribute values applied are those in the Context's Theme and the given AttributeSet 这个方法是通过xml文件来创建一个view对象的时候调用。很显然xml文件一般是布局文件,就是现实控件的时候调用,而布局文件中免不了会有属性的设置,如android:layout_width等,对这些属性的设置对应的处理代码也在这个方法中完成。
3. public void SloopView(Context context, AttributeSet attrs, int defStyleAttr) {} api>=3.0(在布局文件中加入Theme的style中调用)
1. 参数一:上下文
2. 参数二:AttributeSet布局文件属性
3. 参数三:style是指它在当前Application或Activity所用的Theme中的默认Style
4. public void SloopView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {} api>=5.0
3. 示例代码
3.1. public MyView(Context context, AttributeSet attrs) { //调用了三个参数的构造函数,明确指定第三个参数 this(context, attrs, R.attr.MyViewStyle); } public MyView(Context context, AttributeSet attrs, int defStyleAttr) { //此处调了四个参数的构造函数,无视即可 this(context, attrs, defStyleAttr, 0); }
4. 注意
4.1. 即使你在View中使用了Style这个属性也不会调用三个参数的构造函数,所调用的依旧是两个参数的构造函数。由于三个参数的构造函数第三个参数一般不用,暂不考虑,第三个参数的具体用法会在以后用到的时候详细介绍。排除了两个之后,只剩下一个参数和两个参数的构造函数
5. 两个常用构造方法使用场景
1. 调用的是一个参数的构造函数
1.1. MyView view = new MyView(context)
2. 调用的是两个参数的构造函数
2.1.
3. 第三步:在res/vaules/attrs.xml中创建自定义属性
1. reference:参考某一资源ID。 (1)属性定义: (2)属性使用: android:layout_width = "42dip" android:layout_height = "42dip" android:background = "@drawable/图片ID" /> 2. color:颜色值。 (1)属性定义: (2)属性使用: android:layout_width = "42dip" android:layout_height = "42dip" android:textColor = "#00FF00" /> 3. boolean:布尔值。 (1)属性定义: (2)属性使用: android:layout_width = "42dip" android:layout_height = "42dip" android:focusable = "true" /> 4. dimension:尺寸值。 (1)属性定义: (2)属性使用: android:layout_width = "42dip" android:layout_height = "42dip" /> 5. float:浮点值。 (1)属性定义: (2)属性使用: android:fromAlpha = "1.0" android:toAlpha = "0.7" /> 6. integer:整型值。 (1)属性定义: (2)属性使用: xmlns:android = "http://schemas.android.com/apk/res/android" android:drawable = "@drawable/图片ID" android:pivotX = "50%" android:pivotY = "50%" android:framesCount = "12" android:frameDuration = "100" /> 7. string:字符串。 (1)属性定义: (2)属性使用: android:layout_width = "fill_parent" android:layout_height = "fill_parent" android:apiKey = "0jOkQ80oD1JL9C6HAja99uGXCRiS2CGjKO_bc_g" /> 8. fraction:百分数。 (1)属性定义: (2)属性使用: xmlns:android = "http://schemas.android.com/apk/res/android" android:interpolator = "@anim/动画ID" android:fromDegrees = "0" android:toDegrees = "360" android:pivotX = "200%" android:pivotY = "300%" android:duration = "5000" android:repeatMode = "restart" android:repeatCount = "infinite" /> 9. enum:枚举值。 (1)属性定义: (2)属性使用: xmlns:android = "http://schemas.android.com/apk/res/android" android:orientation = "vertical" android:layout_width = "fill_parent" android:layout_height = "fill_parent" > 10. flag:位或运算。 (1)属性定义: (2)属性使用: android:name = ".StyleAndThemeActivity" android:label = "@string/app_name" android:windowSoftInputMode = "stateUnspecified | stateUnchanged | stateHidden"> 注意: 属性定义时可以指定多种类型值。 (1)属性定义: (2)属性使用: android:layout_width = "42dip" android:layout_height = "42dip" android:background = "@drawable/图片ID|#00FF00" />
4. 第四步:重写onMesure (测量的高度和宽度非必要)
1. View的大小不仅由自身所决定,同时也会受到父控件的影响,为了我们的控件能更好的适应各种情况,一般会自己进行测量
2. 示例代码
2.1. @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthsize = MeasureSpec.getSize(widthMeasureSpec); //取出宽度的确切数值 int widthmode = MeasureSpec.getMode(widthMeasureSpec); //取出宽度的测量模式 int heightsize = MeasureSpec.getSize(heightMeasureSpec); //取出高度的确切数值 int heightmode = MeasureSpec.getMode(heightMeasureSpec); //取出高度的测量模式 }
2.2. 说明
2.2.1. 参数一 widthMeasureSpec
2.2.1.1. int值由一个32位的二进制数组成 与控件宽度相关 31-30这两位表示测量模式,29~0这三十位表示宽的实际值
2.2.2. 参数二 int heightMeasureSpec
2.2.2.1. int值由一个32位的二进制数组成 与控件高度相关int值由一个32位的二进制数组成 与控件宽度相关 31-30这两位表示测量模式,29~0这三十位表示高的实际值
2.2.3. onMeasure 函数中有 widthMeasureSpec 和 heightMeasureSpec 这两个 int 类型的参数, 毫无疑问他们是和宽高相关的, 但它们其实不是宽和高, 而是由宽、高和各自方向上对应的测量模式来合成的一个值
3. 测量模式
1. 一共有三种, 被定义在 Android 中的 View 类的一个内部类View.MeasureSpec中
2. 结构图
2.1.
3. 举栗说明
3.1. 以1080为例
3.2.
3.3. 计算工具网站:http://jinzhi.supfree.net/
4. 注意
4.1. 上面的东西了解即可,在实际运用之中只需要记住有三种模式,用 MeasureSpec 的 getSize是获取数值, getMode是获取模式即可
4. 注意
4.1. 如果对View的宽高进行修改了,不要调用super.onMeasure(widthMeasureSpec,heightMeasureSpec);要调用setMeasuredDimension(widthsize,heightsize); 这个函数
5. 第五步:重写onSizeChanged(确定View大小)
5.1. 这个函数在视图大小发生改变时调用
5.2. 示例代码
5.2.1. @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { //四个参数:分别为 宽度,高度,上一次宽度,上一次高度 super.onSizeChanged(w, h, oldw, oldh); }
5.3. 为什么要调用这个方法??? 因为View的大小不仅由View本身控制,而且受父控件的影响,所以我们在确定View大小的时候最好使用系统提供的onSizeChanged回调函数
6. 第六步:重写onDraw(Canvas canvas)方法
1. Paint
1. 画笔主要保存了颜色, 样式等绘制信息,指定了如何绘制文本和图形
2. 分类
1. 图形绘制相关
1. setARGB(int a,int r,int g,int b); 设置绘制的颜色,a代表透明度,r,g,b代表颜色值。
2. setAlpha(int a); 设置绘制图形的透明度。
3. setColor(int color); 设置绘制的颜色,使用颜色值来表示,该颜色值包括透明度和RGB颜色。
4. setAntiAlias(boolean aa); 设置是否使用抗锯齿功能,会消耗较大资源,绘制图形速度会变慢。
5. setDither(boolean dither); 设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰
6. setFilterBitmap(boolean filter); 如果该项设置为true,则图像在动画进行中会滤掉对Bitmap图像的优化操作,加快显示 速度,本设置项依赖于dither和xfermode的设置
7. setMaskFilter(MaskFilter maskfilter); 设置MaskFilter,可以用不同的MaskFilter实现滤镜的效果,如滤化,立体等
8. setColorFilter(ColorFilter colorfilter); 设置颜色过滤器,可以在绘制颜色时实现不用颜色的变换效果
9. setPathEffect(PathEffect effect); 设置绘制路径的效果,如点画线等
10. setShader(Shader shader); 设置图像效果,使用Shader可以绘制出各种渐变效果
11. setShadowLayer(float radius ,float dx,float dy,int color); 在图形下面设置阴影层,产生阴影效果,radius为阴影的角度,dx和dy为阴影在x轴和y轴上的距离,color为阴影的颜色
12. setStyle(Paint.Style style); 设置画笔的样式,为FILL(填充),FILL_OR_STROKE(描边加填充),或STROKE (描边)
13. setStrokeCap(Paint.Cap cap); 当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式,如圆形样式 Cap.ROUND,或方形样式Cap.SQUARE
14. setSrokeJoin(Paint.Join join); 设置绘制时各图形的结合方式,如平滑效果等
15. setStrokeWidth(float width); 当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的粗细度
16. setXfermode(Xfermode xfermode); 设置图形重叠时的处理方式,如合并,取交集或并集,经常用来制作橡皮的擦除效果
2. 文本绘制相关
1. setFakeBoldText(boolean fakeBoldText); 模拟实现粗体文字,设置在小字体上效果会非常差
2. setSubpixelText(boolean subpixelText); 设置该项为true,将有助于文本在LCD屏幕上的显示效果
3. setTextAlign(Paint.Align align); 设置绘制文字的对齐方向
4. setTextScaleX(float scaleX); 设置绘制文字x轴的缩放比例,可以实现文字的拉伸的效果
5. setTextSize(float textSize); 设置绘制文字的字号大小
6. setTextSkewX(float skewX); 设置斜体文字,skewX为倾斜弧度
7. setTypeface(Typeface typeface); 设置Typeface对象,即字体风格,包括粗体,斜体以及衬线体,非衬线体等
8. setUnderlineText(boolean underlineText); 设置带有下划线的文字效果
9. setStrikeThruText(boolean strikeThruText); 设置带有删除线的效果
3. 常用属性
1. setStyle(Paint.Style.FILL); //设置画笔模式为填充
1.1. FILL 填充
1.2. STROKE 描边(注意只有闭环的才有效果)
1.3. FILL_AND_STROKE 描边加填充
2. setColor(Color.BLUE);设置画笔颜色
3. setAntiAlias(true);
2. 步骤一 创建画笔
1. private Paint mPaint = new Paint();
2. private void initPaint() { mPaint.setColor(Color.Red); //设置画笔颜色 mPaint.setStyle(Paint.Style.FILL); //设置画笔模式为填充 mPaint.setStrokeWidth(10f); //设置画笔宽度为10px }
3. 建议在构造方法中创建 画笔,尽量不要在onDraw方法中初始化,因为onDraw方法可能会多次调用
3. 步骤二 绘制(Canvas)
1. Canvas翻译成中文画布,能够在上面绘制各种东西,是安卓平台2D图形绘制的基础,非常强大
1. 1.可操作性强:由于这些是构成上层的基础,所以可操作性必然十分强大。
2. 2.比较难用:各种方法太过基础,想要完美的将这些操作组合起来有一定难度
2. 主要功能
1. 绘制相关
1.1.
2. 绘制基本图形
1. 绘制背景颜色
1. drawColor(int color)
2. drawColor(int color, PorterDuff.Mode mode)
2.1. PorterDuff.Mode
2.1.1. 取值
2.1.1.1. 1.PorterDuff.Mode.CLEAR // 所绘制不会提交到画布上。
2.1.1.2. 2.PorterDuff.Mode.SRC// 显示上层绘制图片
2.1.1.3. 3.PorterDuff.Mode.DST// 显示下层绘制图片
2.1.1.4. 4.PorterDuff.Mode.SRC_OVER// 正常绘制显示,上下层绘制叠盖。
2.1.1.5. 5.PorterDuff.Mode.DST_OVER// 上下层都显示。下层居上显示。
2.1.1.6. 6.PorterDuff.Mode.SRC_IN// 取两层绘制交集。显示上层。
2.1.1.7. 7.PorterDuff.Mode.DST_IN// 取两层绘制交集。显示下层。
2.1.1.8. 8.PorterDuff.Mode.SRC_OUT/ 取上层绘制非交集部分。
2.1.1.9. 9.PorterDuff.Mode.DST_OUT// 取下层绘制非交集部分。
2.1.1.10. 10.PorterDuff.Mode.SRC_ATOP// 取下层非交集部分与上层交集部分
2.1.1.11. 11.PorterDuff.Mode.DST_ATOP// 取上层非交集部分与下层交集部分
2.1.1.12. 12.PorterDuff.Mode.XOR// 异或:去除两图层交集部分
2.1.1.13. 13.PorterDuff.Mode.DARKEN// 取两图层全部区域,交集部分颜色加深
2.1.1.14. 14.PorterDuff.Mode.LIGHTEN// 取两图层全部,点亮交集部分颜色
2.1.1.15. 15.PorterDuff.Mode.MULTIPLY// 取两图层交集部分叠加后颜色
2.1.1.16. 16.PorterDuff.Mode.SCREEN // 取两图层全部区域,交集部分变为透明色
2.1.2. 效果图
2.1.2.1.
2. 绘制点
1. canvas.drawPoint(200, 200, mPaint);
2. canvas.drawPoints(new float[]{ 500,500, 500,600, 500,700 },mPaint) //绘制一组点,坐标位置由float数组指定
3. 绘制直线
1. canvas.drawLine(300,300,500,600,mPaint);
2. canvas.drawLines(new float[]{ 100,200,200,200, 100,300,200,300 },mPaint); // 绘制一组线 每四数字(两个点的坐标)确定一条线
4. 绘制矩形
1. drawRect(@NonNull RectF rect, @NonNull Paint paint)
2. drawRect(@NonNull Rect r, @NonNull Paint paint)
最大的区别就是精度不同,Rect是int(整形)的,而RectF是float(单精度浮点型)的
3. drawRect(float left, float top, float right, float bottom, Paint paint)
5. 绘制圆角矩形
1. drawRoundRect( RectF rect, float rx, float ry, Paint paint)
2. drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint)
6. 绘制椭圆
1. // 第一种 drawOval(RectF oval, Paint paint)
2. //第二种(API>21) drawOval(float left, float top, float right, float bottom, Paint paint)
7. 绘制圆
1. drawCircle(float cx, float cy, float radius, Paint paint)
1.1. 前两个是圆心坐标
1.2. 第三个是半径
1.3. 第四个是画笔
8. 绘制圆弧
1. drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint)
1.1. startAngle // 开始角度
1.2. sweepAngle // 扫过角度
1.3. useCenter // 是否使用中心
2. drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint)(API>21)
9. 综合练习
9.1. public class PieView extends View { // 颜色表(注意: 此处定义颜色使用的是ARGB,带Alpha通道的) private int[] mColors = {0xFFCCFF00, 0xFF6495ED, 0xFFE32636, 0xFF800000, 0xFF808000, 0xFFFF8C69, 0xFF808080, 0xFFE6B800, 0xFF7CFC00}; // 饼状图初始绘制角度 private float mStartAngle = 0; // 数据 private ArrayList<PieData> mData; // 宽高 private int mWidth, mHeight; // 画笔 private Paint mPaint = new Paint(); public PieView(Context context) { this(context, null); } public PieView(Context context, AttributeSet attrs) { super(context, attrs); mPaint.setStyle(Paint.Style.FILL); mPaint.setAntiAlias(true); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (null == mData) return; float currentStartAngle = mStartAngle; // 当前起始角度 canvas.translate(mWidth / 2, mHeight / 2); // 将画布坐标原点移动到中心位置 float r = (float) (Math.min(mWidth, mHeight) / 2 * 0.8); // 饼状图半径 RectF rect = new RectF(-r, -r, r, r); // 饼状图绘制区域 for (int i = 0; i < mData.size(); i++) { PieData pie = mData.get(i); mPaint.setColor(pie.getColor()); canvas.drawArc(rect, currentStartAngle, pie.getAngle(), true, mPaint); currentStartAngle += pie.getAngle(); } } // 设置起始角度 public void setStartAngle(int mStartAngle) { this.mStartAngle = mStartAngle; invalidate(); // 刷新 } // 设置数据 public void setData(ArrayList<PieData> mData) { this.mData = mData; initDate(mData); invalidate(); // 刷新 } // 初始化数据 private void initDate(ArrayList<PieData> mData) { if (null == mData || mData.size() == 0) // 数据有问题 直接返回 return; float sumValue = 0; for (int i = 0; i < mData.size(); i++) { PieData pie = mData.get(i); sumValue += pie.getValue(); //计算数值和 int j = i % mColors.length; //设置颜色 pie.setColor(mColors[j]); } float sumAngle = 0; for (int i = 0; i < mData.size(); i++) { PieData pie = mData.get(i); float percentage = pie.getValue() / sumValue; // 百分比 float angle = percentage * 360; // 对应的角度 pie.setPercentage(percentage); // 记录百分比 pie.setAngle(angle); // 记录角度大小 sumAngle += angle; Log.i("angle", "" + pie.getAngle()); } } }
3. 绘制画布操作
1. 位移(translate)
2. 缩放(scale)
3. 注意:所有的画布操作都只影响后续的绘制,对之前已经绘制过的内容没有影响
4. 旋转(rotate)
5. 错切(skew)
6. 快照(save)
7. 回滚(restore)
4. 绘制图片文字
1. 绘制图片
1. drawPicture
1. Picture看作是一个录制Canvas操作的录像机,录的是Canvas中绘制的内容
2. 常用方法
1. public Canvas beginRecording (int width, int height) 开始录制 (返回一个Canvas,在Canvas中所有的绘制都会存储在Picture中)
2. public void endRecording () 结束录制
3. public void draw (Canvas canvas) 将Picture中内容绘制到Canvas中
2. drawBitmap
2. 绘制文字
5. 绘制Path操作
1. 基本操作
2. 贝塞尔曲线
3. PathMeasure
6. 操作相关
6.1.
4. 示例代码
7. 第七步:使用在布局文件中采(用包名+类名)的方式声明
3. 案例
1. 饼状图
1. 效果图
1.1.
2. 代码
2.1. public class PieView extends View { // 颜色表 private int[] mColors = {0xFFCCFF00, 0xFF6495ED, 0xFFE32636, 0xFF800000, 0xFF808000, 0xFFFF8C69, 0xFF808080, 0xFFE6B800, 0xFF7CFC00}; // 饼状图初始绘制角度 private float mStartAngle = 0; // 数据 private ArrayList<PieData> mData; // 宽高 private int mWidth, mHeight; // 画笔 private Paint mPaint = new Paint(); public PieView(Context context) { this(context, null); } public PieView(Context context, AttributeSet attrs) { super(context, attrs); mPaint.setStyle(Paint.Style.FILL); mPaint.setAntiAlias(true); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (null == mData) return; float currentStartAngle = mStartAngle; // 当前起始角度 canvas.translate(mWidth / 2, mHeight / 2); // 将画布坐标原点移动到中心位置 float r = (float) (Math.min(mWidth, mHeight) / 2 * 0.8); // 饼状图半径 RectF rect = new RectF(-r, -r, r, r); // 饼状图绘制区域 for (int i = 0; i < mData.size(); i++) { PieData pie = mData.get(i); mPaint.setColor(pie.getColor()); canvas.drawArc(rect, currentStartAngle, pie.getAngle(), true, mPaint); currentStartAngle += pie.getAngle(); } } // 设置起始角度 public void setStartAngle(int mStartAngle) { this.mStartAngle = mStartAngle; invalidate(); // 刷新 } // 设置数据 public void setData(ArrayList<PieData> mData) { this.mData = mData; initDate(mData); invalidate(); // 刷新 } // 初始化数据 private void initDate(ArrayList<PieData> mData) { if (null == mData || mData.size() == 0) // 数据有问题 直接返回 return; float sumValue = 0; for (int i = 0; i < mData.size(); i++) { PieData pie = mData.get(i); sumValue += pie.getValue(); //计算数值和 int j = i % mColors.length; //设置颜色 pie.setColor(mColors[j]); } float sumAngle = 0; for (int i = 0; i < mData.size(); i++) { PieData pie = mData.get(i); float percentage = pie.getValue() / sumValue; // 百分比 float angle = percentage * 360; // 对应的角度 pie.setPercentage(percentage); // 记录百分比 pie.setAngle(angle); // 记录角度大小 sumAngle += angle; Log.i("angle", "" + pie.getAngle()); } } }
2. 圆形进度条
1. 代码
1.1. /** * 圆形progressBar进度条,线程安全的View,可直接在线程中更新进度 * */ public class CircleProgressBar extends View { private Context mContext; /** * 画笔对象的引用 */ private Paint paint; /** * 圆环的颜色 */ private int roundColor; /** * 圆环进度的颜色 */ private int roundProgressColor; /** * 中间进度百分比的字符串的颜色 */ private int textColor; /** * 中间进度百分比的字符串的字体 */ private float textSize; /** * 圆环的宽度 */ private float roundWidth; /** * 最大进度 */ private int max; /** * 当前进度 */ private int progress; /** * 是否显示中间的进度 */ private boolean textIsDisplayable; /** * 进度的风格,实心或者空心 */ private int style; public static final int STROKE = 0; public static final int FILL = 1; public CircleProgressBar(Context context) { this(context, null); this.mContext = context; } public CircleProgressBar(Context context, AttributeSet attrs) { this(context, attrs, 0); this.mContext = context; } public CircleProgressBar(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); this.mContext = context; paint = new Paint(); TypedArray mTypedArray = context.obtainStyledAttributes(attrs, net.canking.circlepregressbar.R.styleable.CircleProgressBar); // 获取自定义属性和默认值 roundColor = mTypedArray.getColor(R.styleable.CircleProgressBar_roundColor, Color.RED); roundProgressColor = mTypedArray.getColor(R.styleable.CircleProgressBar_roundProgressColor, Color.GREEN); textColor = mTypedArray.getColor(R.styleable.CircleProgressBar_textColor, Color.GREEN); textSize = mTypedArray.getDimension(R.styleable.CircleProgressBar_textSize, 15); roundWidth = mTypedArray.getDimension(R.styleable.CircleProgressBar_roundWidth, 5); max = mTypedArray.getInteger(R.styleable.CircleProgressBar_max, 100); textIsDisplayable = mTypedArray.getBoolean(R.styleable.CircleProgressBar_textIsDisplayable, true); style = mTypedArray.getInt(R.styleable.CircleProgressBar_style, 0); mTypedArray.recycle(); } private int dip2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); /** * 画最外层的大圆环 */ int centre = getWidth() / 2; // 获取圆心的x坐标 int radius = (int) (centre - roundWidth / 2); // 圆环的半径 paint.setColor(roundColor); // 设置圆环的颜色 paint.setStyle(Paint.Style.STROKE); // 设置空心 paint.setStrokeWidth(roundWidth); // 设置圆环的宽度 paint.setAntiAlias(true); // 消除锯齿 canvas.drawCircle(centre, centre, radius, paint); // 画出圆环 Log.e("log", centre + ""); /** * 画进度百分比 */ paint.setStrokeWidth(0); paint.setColor(textColor); int tTextSise = dip2px(mContext, textSize); paint.setTextSize(tTextSise); paint.setTypeface(Typeface.DEFAULT_BOLD); // 设置字体 int percent = (int) (((float) progress / (float) max) * 100); // 中间的进度百分比,先转换成float在进行除法运算,不然都为0 float textWidth = paint.measureText(percent + "%"); // 测量字体宽度,我们需要根据字体的宽度设置在圆环中间 if (textIsDisplayable && percent != 0 && style == STROKE) { canvas.drawText(percent + "%", centre - textWidth / 2, centre + tTextSise / 2, paint); // 画出进度百分比 } /** * 画圆弧 ,画圆环的进度 */ // 设置进度是实心还是空心 paint.setStrokeWidth(roundWidth); // 设置圆环的宽度 paint.setColor(roundProgressColor); // 设置进度的颜色 RectF oval = new RectF(centre - radius, centre - radius, centre + radius, centre + radius); // 用于定义的圆弧的形状和大小的界限 switch (style) { case STROKE: { paint.setStyle(Paint.Style.STROKE); canvas.drawArc(oval, 0, 360 * progress / max, false, paint); // 根据进度画圆弧 break; } case FILL: { paint.setStyle(Paint.Style.FILL_AND_STROKE); if (progress != 0) canvas.drawArc(oval, 0, 360 * progress / max, true, paint); // 根据进度画圆弧 break; } } } public synchronized int getMax() { return max; } /** * 设置进度的最大值 * * @param max */ public synchronized void setMax(int max) { if (max < 0) { throw new IllegalArgumentException("max not less than 0"); } this.max = max; } /** * 获取进度.需要同步 * * @return */ public synchronized int getProgress() { return progress; } /** * 设置进度,此为线程安全控件,由于考虑多线的问题,需要同步 * 刷新界面调用postInvalidate()能在非UI线程刷新 * * @param progress */ public synchronized void setProgress(int progress) { if (progress < 0) { throw new IllegalArgumentException("progress not less than 0"); } if (progress > max) { progress = max; } if (progress <= max) { this.progress = progress; postInvalidate(); } } public int getCricleColor() { return roundColor; } public void setCricleColor(int cricleColor) { this.roundColor = cricleColor; } public int getCricleProgressColor() { return roundProgressColor; } public void setCricleProgressColor(int cricleProgressColor) { this.roundProgressColor = cricleProgressColor; } public int getTextColor() { return textColor; } public void setTextColor(int textColor) { this.textColor = textColor; } public float getTextSize() { return textSize; } public void setTextSize(float textSize) { this.textSize = textSize; } public float getRoundWidth() { return roundWidth; } public void setRoundWidth(float roundWidth) { this.roundWidth = roundWidth; } }
2. 效果图
2.1.
3. Loading界面
3.1. http://blog.csdn.net/tianjian4592/article/details/44538605
4. github地址:https://github.com/CankingApp/CircleProgressBar/blob/master/CircleProgressBar/src/net/canking/circleprogressbar/CircleProgressBar.java
4. 总结(基本步骤)
1. 构造函数 初始化(初始化画笔Paint)
2. onMeasure 测量View的大小(非必要)
3. onSizeChanged 确定View大小(记录当前View的宽高)(非必要)
4. onDraw 实际绘制内容
5. 提供接口 提供接口(提供设置数据的接口)(非必要)
2. ViewGroup
1. 简介
1. A ViewGroup is a special view that can contain other views (called children.) The view group is the base class for layouts and views containers.
2. ViewGroup是一种可以包含其他视图的特殊视图,是布局和其他视图容器的基类(容器控件)
2. 定义
1. 自定义ViewGroup一般是利用现有的组件根据特定的布局方式来组成新的组件或者继承自ViewGroup或各种Layout,包含有子View
3. 绘制流程
1. View跟ViewGroup的显示过程
1. 在我们通过Activity的setContentView设置了Activity的页面内容以后,随着Activity的onResume方法的调用,整个Activity的内容就会显示到屏幕上,那么在显示之前,整个显示过程经历了哪些阶段呢?
2. 主要包含以下几个阶段
2.1. 整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为 根据之前设置的状态,判断是否需要重新计算视图大小(measure)、是否重新需要安置视图的位置(layout)、以及是否需要重绘 (draw)
2.1.1.
2.2. 1 、调用View的measure()
2.2.1. 主要作用:为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性: mMeasureWidth),每个View的控件的实际宽高都是由父视图和本身视图决定的
2.2.2. 流程
2.2.2.1. 1、设置本View视图的最终大小,该功能的实现通过调用setMeasuredDimension()方法去设置实际的高(对应属性mMeasuredHeight)和宽(对应属性:mMeasureWidth)
2.2.2.2. 2 、如果该View对象是个ViewGroup类型,需要重写该onMeasure()方法,对其子视图进行遍历的measure()过程。
2.2.2.3. 2.1 对每个子视图的measure()过程,是通过调用父类ViewGroup.java类里的measureChildWithMargins()方法去实现,该方法内部只是简单地调用了View对象的measure()方法。(由于measureChildWithMargins()方法只是一个过渡层更简单的做法是直接调用View对象的measure()方法)。
2.2.2.4. 整个measure调用流程就是个树形的递归过程
2.2.3. 注意:measure函数原型为 View.java 该函数不能被重载
2.3. 2 、调用View的onMeasure()
mesarue()(测量)过程
2.4. 3 、调用View的layout()
2.4.1. 主要作用 :为将整个根据子视图的大小以及布局参数将View树放到合适的位置上。
2.4.2. 主要流程
2.4.2.1. 1 、layout方法会设置该View视图位于父视图的坐标轴,即mLeft,mTop,mLeft,mBottom(调用setFrame()函数去实现) 接下来回调onLayout()方法(如果该View是ViewGroup对象,需要实现该方法,对每个子视图进行布局) ;
2.4.2.2. 2、如果该View是个ViewGroup类型,需要遍历每个子视图chiildView,调用该子视图的layout()方法去设置它的坐标值。
2.5. 4 、调用View的onLayout()
布局过程
2.6. 5 、调用View的draw()
2.6.1. ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View树,值得注意的是每次发起绘图时,并不会重新绘制每个View树的视图,而只会重新绘制那些“需要重绘”的视图,View类内部变量包含了一个标志位DRAWN,当该视图需要重绘时,就会为该View添加该标志位。
2.7. 6 、调用View的onDraw()
draw()绘图过程
2. 绘制布局由两个遍历过程组成
1. 测量过程
1.1. 第一个过程测量过程由measure(int, int)方法完成,该方法从上到下遍历视图树。在递归遍历过程中,每个视图都会向下层传递尺寸和规格。当measure方法遍历结束,每个视图都保存了各自的尺寸信息。
2. 布局过程
2.1. 第二个过程由layout(int,int,int,int)方法完成,该方法也是由上而下遍历视图树,在遍历过程中,每个父视图通过测量过程的结果定位所有子视图的位置信息。
3. 简而言之,第一步是测量ViewGroup的宽度和高度,在onMeasure()方法中完成,ViewGroup遍历所有子视图计算出它的大小。第二步是根据第一步获取的尺寸去布局所有子视图,在onLayout()中完成
4. 重要类介绍
1. View 类
1. View(Context) 动态实例化控件的构造方法
2. View(Context context, AttributeSet attrs) 布局中使用控件的构造的方法
3. onMeasure(int widthMeasureSpec, int heightMeasureSpec) 自定义控件时重写的方法,用于测量控件大小
4. onDraw(Canvas canvas) 在自义控件上绘制相关内容
5. onTouchEvent(MotionEvent) 实现触摸事件处理方法
6. setPadding(int l,int t,int r,int b) 设置内部间距
7. layout(int l, int t, int r,int b) 改变控件位置的方法
8. requestLayout() //清除布局,获取新的布局空间
9. invalidate() //重新绘制新的数据
刷新UI,重新调用onDraw()方法绘制UI
2. Canvas 画布类
1. canvas.drawText(String,int x,int y,Paint) 绘制文本
2. canvas.drawRoundRect(RectF,int rx,int ry,Paint) 绘制圆角四边方形
3. canvas.drawBitmap(bitmap, left, top, paint) 绘制图片
4. canvas.drawCircle(cx, cy, radius, paint) 绘制圆
4.1. cx:圆心X坐标
4.2. cy:圆心Y坐标
4.3. radius:半径
5. canvas.drawLine(startX, startY, stopX, stopY, paint) 绘制线条
6. public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)(扇形)
1. oval :指定圆弧的外轮廓矩形区域。
2. startAngle: 圆弧起始角度,单位为度。
3. sweepAngle: 圆弧扫过的角度,顺时针方向,单位为度。
4. useCenter: 如果为True时,在绘制圆弧时将圆心包括在内,通常用来绘制扇形。
5. paint: 绘制圆弧的画板属性,如颜色,是否填充等。
3. MeasureSpec 测量空间工具类
1. 一个MeasureSpec封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求。一个MeasureSpec由大小和模式组成
2. int MeasureSpec.getMode(mSpec) 获取控件大小模式
1. MeasureSpec.EXACTLY,确切空间,父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小,布局中的属性值一般为match_parent或160dp固定值
2. MeasureSpec.AT_MOST 尽量多的空间,子元素至多达到指定大小的值。布局中的属性值wrap_content
3. MeasureSpec.UNSPECIFIED 未指定的,父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小
3. MeasureSpec.getSize(mSpec) 获取控件大小
4. Paint 画笔类
1. 大体上可以分为两类,一类与图形绘制相关,一类与文本绘制相关。
2. 图形绘制
1. setARGB(int a,int r,int g,int b); // 设置绘制的颜色,a代表透明度,r,g,b代表颜色值。
2. setAlpha(int a); //设置绘制图形的透明度。
3. setColor(int color); // 设置绘制的颜色,使用颜色值来表示,该颜色值包括透明度和RGB颜色。
4. setAntiAlias(boolean aa); //设置是否使用抗锯齿功能,会消耗较大资源,绘制图形速度会变慢。
5. setDither(boolean dither); //设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰
6. setFilterBitmap(boolean filter); //如果该项设置为true,则图像在动画进行中会滤掉对Bitmap图像的优化操作,加快显示 速度,本设置项依赖于dither和xfermode的设置
7. setMaskFilter(MaskFilter maskfilter); //设置MaskFilter,可以用不同的MaskFilter实现滤镜的效果,如滤化,立体等 *
8. setColorFilter(ColorFilter colorfilter); // 设置颜色过滤器,可以在绘制颜色时实现不用颜色的变换效果
9. setPathEffect(PathEffect effect); //设置绘制路径的效果,如点画线等
10. setShader(Shader shader); // 设置图像效果,使用Shader可以绘制出各种渐变效果
11. setShadowLayer(float radius ,float dx,float dy,int color); //在图形下面设置阴影层,产生阴影效果,radius为阴影的角度,dx和dy为阴影在x轴和y轴上的距离,color为阴影的颜色
12. setStyle(Paint.Style style); //设置画笔的样式,为FILL,FILL_OR_STROKE,或STROKE Style.FILL: 实心 STROKE:空心 FILL_OR_STROKE:同时实心与空心
13. setStrokeCap(Paint.Cap cap); // 当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式,如圆形样式 Cap.ROUND,或方形样式Cap.SQUARE
14. setSrokeJoin(Paint.Join join); //设置绘制时各图形的结合方式,如平滑效果等
15. setStrokeWidth(float width); //当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的粗细度
16. setXfermode(Xfermode xfermode); //设置图形重叠时的处理方式,如合并,取交集或并集,经常用来制作橡皮的擦除效果
3. 文本绘制
3.1. setFakeBoldText(boolean fakeBoldText); //模拟实现粗体文字,设置在小字体上效果会非常差
3.2. setSubpixelText(boolean subpixelText); //设置该项为true,将有助于文本在LCD屏幕上的显示效果
3.3. setTextAlign(Paint.Align align); 设置绘制文字的对齐方向
3.4. setTextScaleX(float scaleX); //设置绘制文字x轴的缩放比例,可以实现文字的拉伸的效果
3.5. setTextSize(float textSize); //设置绘制文字的字号大小 单位像素,一般转成sp
3.6. setTextSkewX(float skewX); //设置斜体文字,skewX为倾斜弧度
3.7. setTypeface(Typeface typeface); //设置Typeface对象,即字体风格,包括粗体,斜体以及衬线体,非衬线体等
3.8. setUnderlineText(boolean underlineText); //设置带有下划线的文字效果
3.9. setStrikeThruText(boolean strikeThruText); //设置带有删除线的效果
4. 其他
4.1. float mTextPaint.measureText(String) 测量文字的大小
4.2. Paint.ascent() 获取文字基准线以上到文字顶部的间距,负数
4.3. Paint.descent() 获取文字基准线以下到文字底部的间距
5. 常用
1. setAntiAlias(); //设置画笔的锯齿效果
2. setColor(); //设置画笔的颜色
3. setARGB(); //设置画笔的A、R、G、B值
4. setAlpha(); //设置画笔的Alpha值
5. setTextSize(); //设置字体的尺寸
6. setStyle(); //设置画笔的风格(空心或实心)
7. setStrokeWidth(); //设置空心边框的宽度
5. TypedArray 属性数组类
1. TypeArray Context.obtainStyledAttributes(attrs,R.styleable.LabelView) 获取指定attrs中的所有属性
2. String getString(R.styleable.LabelView_text) 获取指定属性的文本值
3. int getDimensionPixelSize(R.styleable.LabelView_textSize, 0) 获取指定属性的间距值
4. int getColor(R.styleable.LabelView_textColor, 0xFF000000) 获取指定属性的颜色值
6. Matrix
1. 简介:Matrix是一个矩阵,主要功能是坐标映射,数值转换。
5. 实例
6. 参考资料
1. 流程图
1.1.
2. https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B02%5DCanvas_BasicGraphics.md
第27天(自定义View)
Design
CoordinatorLayout
简介
CoordinatorLayout is a super-powered FrameLayout 是一个增强FrameLayout
CoordinatorLayout is intended for two primary use cases 主要作用有两个
As a top-level application decor or chrome layout 作为一个布局的根布局
As a container for a specific interaction with one or more child views 作为一个为子视图之间相互协调手势效果的一个协调布局
使用
CoordinatorLayout与FloatingActionButton
CoordinatorLayout与AppBarLayout
1 AppBarLayout嵌套TabLayout
2 AppBarLayout嵌套CollapsingToolbarLayout
Behavior
Behavior是CoordinatorLayout用来和各个子View通信用的代理类
代理图
自定义behavior
案例
CoordinatorLayout实现Toolbar隐藏效果
最外层我们用CoordinatorLayout 来是做整体的布局,AppBarLayout将Toolbar和TabLayout整合成了一个整体
在需要滚动隐藏的View 设置 app:layout_scrollFlags=”scroll|enterAlways”这个属性,设置滚动事件,属性里面必须至少启用scroll这个flag,这样这个view才会滚动出屏幕,否则它将一直固定在顶部
给滑动的组件设置app:layout_behavior属性
滑动组件必须包含RecyclerView 或者NestedScrollView
该属性的值实际上是一个完整的class名字,而上面例子中的 @string/appbar_scrolling_view_behavior 是Android Support Library 定义后的值,可以被直接使用
xml属性
android:layout_gravity="bottom"//控制组件在整个布局中的位置
app:layout_scrollFlags=”scroll|enterAlways 作用于子类控件布局文件中可添加多个属性
scroll: 所有想滚动出屏幕的view都需要设置这个flag- 没有设置这个flag的view将被固定在屏幕顶部。
enterAlways: 这个flag让任意向下的滚动都会导致该view变为可见,启用快速“返回模式”。
enterAlwaysCollapsed: 当你的视图已经设置minHeight属性又使用此标志时,你的视图只能已最小高度进入,只有当滚动视图到达顶部时才扩大到完整高度。
exitUntilCollapsed: 滚动退出屏幕,最后折叠在顶端。
app:layout_behavior="@string/appbar_scrolling_view_behavior" 例如在toolbar上添加这个属性,可以toolbar实现滚动隐藏
注意
CoordinatorLayout必须作为整个布局的父布局容器。
给需要滑动的组件设置 app:layout_scrollFlags=”scroll|enterAlways” 属性。
给你的可滑动的组件,也就是RecyclerView 或者 NestedScrollView 设置如下属性:app:layout_behavior="@string/appbar_scrolling_view_behavior"
AppBarLayout
继承LinerLayout实现的一个ViewGroup容器组件,它是为了Material Design设计的App Bar,支持手势滑动操作
CollapsingToolbarLayout
CollapsingToolbarLayout作用是提供了一个可以折叠的Toolbar,它继承至FrameLayout,给它设置layout_scrollFlags,它可以控制包含在CollapsingToolbarLayout中的控件(如:ImageView、Toolbar)在响应layout_behavior事件时作出相应的scrollFlags滚动事件(移除屏幕或固定在屏幕顶端)。
使用步骤
1、在CollapsingToolbarLayout中
layout_scrollFlags
scroll - 想滚动就必须设置这个。
enterAlways - 实现quick return效果, 当向下移动时,立即显示View(比如Toolbar)。
exitUntilCollapsed - 向上滚动时收缩View,但可以固定Toolbar一直在上面。
enterAlwaysCollapsed - 当你的View已经设置minHeight属性又使用此标志时,你的View只能以最小高度进入,只有当滚动视图到达顶部时才扩大到完整高度
contentScrim - 设置当完全CollapsingToolbarLayout折叠(收缩)后的背景颜色。
expandedTitleMarginStart - 设置扩张时候(还没有收缩时)title向左填充的距离。
2、在ImageView控件中
layout_collapseMode
parallax - 设置为这个模式时,在内容滚动时,CollapsingToolbarLayout中的View(比如ImageView)也可以同时滚动,实现视差滚动效果,通常和layout_collapseParallaxMultiplier(设置视差因子)搭配使用
3、在Toolbar控件中
layout_collapseMode
pin - 设置为这个模式时,当CollapsingToolbarLayout完全收缩后,Toolbar还可以保留在屏幕上。
注意事项
使用CollapsingToolbarLayout时必须把title设置到CollapsingToolbarLayout上,设置到Toolbar上不会显示
mCollapsingToolbarLayout.setTitle(" ");
改变变title的字体颜色:扩张时候的title颜色:mCollapsingToolbarLayout.setExpandedTitleColor();
收缩后在Toolbar上显示时的title的颜色:mCollapsingToolbarLayout.setCollapsedTitleTextColor();
属性和方法
Collapsing title:ToolBar的标题,当CollapsingToolbarLayout全屏没有折叠时,title显示的是大字体,在折叠的过程中,title不断变小到一定大小的效果。你可以调用setTitle(CharSequence)方法设置title。
Content scrim:ToolBar被折叠到顶部固定时候的背景,你可以调用setContentScrim(Drawable)方法改变背景或者 在属性中使用 app:contentScrim=”?attr/colorPrimary”来改变背景。
Status bar scrim:状态栏的背景,调用方法setStatusBarScrim(Drawable)。这个只能在Android4.4以上系统有效果。
Parallax scrolling children:CollapsingToolbarLayout滑动时,子视图的视觉差,可以通过属性app:layout_collapseParallaxMultiplier=”0.6”改变。
CollapseMode :子视图的折叠模式,有两种“pin”:固定模式,在折叠的时候最后固定在顶端;“parallax”:视差模式,在折叠的时候会有个视差折叠的效果。我们可以在布局中使用属性app:layout_collapseMode=”parallax”来改变。
TabLayout
Tabs选项卡,效果类似网易新闻客户端的Tab。其实实现Tabs选项卡的效果有很多中方法,Github上也有很多好 用的开源控件,只是这次谷歌把它官方化了,使得开发者无需引用第三方库,就能方便的使用 desgin包下
常用的属性有三个:
app:tabSelectedTextColor:Tab被选中字体的颜色
app:tabTextColor:Tab未被选中字体的颜色
app:tabIndicatorColor:Tab指示器下标的颜色
常用方法
1. addTab(TabLayout.Tab tab, int position, boolean setSelected) 增加选项卡到 layout 中
2. addTab(TabLayout.Tab tab, boolean setSelected) 同上
3. addTab(TabLayout.Tab tab) 同上
4. getTabAt(int index) 得到选项卡
5. getTabCount() 得到选项卡的总个数
6. getTabGravity() 得到 tab 的 Gravity
7. getTabMode() 得到 tab 的模式
8. getTabTextColors() 得到 tab 中文本的颜色
9. newTab() 新建个 tab
10. removeAllTabs() 移除所有的 tab
11. removeTab(TabLayout.Tab tab) 移除指定的 tab
12. removeTabAt(int position) 移除指定位置的 tab
13. setOnTabSelectedListener(TabLayout.OnTabSelectedListener onTabSelectedListener) 为每个 tab 增加选择监听器
14. setScrollPosition(int position, float positionOffset, boolean updateSelectedText) 设置滚动位置
15. setTabGravity(int gravity) 设置 Gravity
16. setTabMode(int mode)
16.1. TabLayout.MODE_SCROLLABLE
16.2. TabLayout.MODE_FIXED
16.3. 分别表示当tab的内容超过屏幕宽度是否支持横向水平滑动,第一种支持滑动,第二种不支持,默认不支持水平滑动
17. setTabTextColors(ColorStateList textColor) 设置 tab 中文本的颜色
18. setTabTextColors(int normalColor, int selectedColor) 设置 tab 中文本的颜色 默认 选中
19. setTabsFromPagerAdapter(PagerAdapter adapter) 设置 PagerAdapter
20. setupWithViewPager(ViewPager viewPager) 和 ViewPager 联动
第七周
第31天
OkHttp
简介
1. OkHttp是一个高效的HTTP库
2. 支持 SPDY ,共享同一个Socket来处理同一个服务器的所有请求
2.1. SPDY并不是首字母缩略词,而是“speedy”(迅速)的缩写。 SPDY协议是Google提出的基于传输控制协议(TCP)的应用层协议,通过压缩、多路复用和优先级来缩短加载时间。GoogleChrome、MozillaFirefox以及Opera已默认开启SPDY。Google曾经称它的测试显示,页面载入提高了一倍。该协议是一种更加快速的内容传输协议
3. 如果SPDY不可用,则通过连接池来减少请求延时
4. 无缝的支持GZIP来减少数据流量
5. 缓存响应数据来减少重复的网络请求
6. socket自动选择最好路线,并支持自动重连
7. 拥有自动维护的socket连接池,减少握手次数
特点
OKHttp是Android版Http客户端。非常高效,支持SPDY、连接池、GZIP和 HTTP 缓存。
默认情况下,OKHttp会自动处理常见的网络问题,像二次连接、SSL的握手问题。
如果你的应用程序中集成了OKHttp,Retrofit默认会使用OKHttp处理其他网络层请求。
从Android4.4开始HttpURLConnection的底层实现采用的是okHttp.
功能介绍
1. 一般的get请求
2. 一般的post请求
3. 基于Http的文件上传
4. 文件下载
5. 加载图片
6. 支持请求回调,直接返回对象、对象集合
7. 支持session的保持
基础使用
GET请求
//创建okHttpClient对象 OkHttpClient client = new OkHttpClient(); //创建一个Request final Request request = new Request.Builder() .url("https://github.com/hongyangAndroid") .build(); //new call Call call = mOkHttpClient.newCall(request); //异步请求加入调度 call.enqueue(new Callback() { @Override public void onFailure(Request request, IOException e) { } @Override public void onResponse(final Response response) throws IOException { //String result = response.body().string(); } });
POST请求
提交JSON数据
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); OkHttpClient client = new OkHttpClient(); String post(String url, String json) throws IOException { RequestBody body = RequestBody.create(JSON, json); Request request = new Request.Builder() .url(url) .post(body) .build(); Response response = client.newCall(request).execute(); f (response.isSuccessful()) { return response.body().string(); } else { throw new IOException("Unexpected code " + response); } }
提交键值对
OkHttpClient client = new OkHttpClient(); String post(String url, String json) throws IOException { RequestBody formBody = new FormEncodingBuilder() .add("platform", "android") .add("name", "bug") .add("subject", "XXXXXXXXXXXXXXX") .build(); Request request = new Request.Builder() .url(url) .post(body) .build(); Response response = client.newCall(request).execute(); if (response.isSuccessful()) { return response.body().string(); } else { throw new IOException("Unexpected code " + response); } }
文件下载
文件上传
File file = new File(Environment.getExternalStorageDirectory(), "aaa.mp4"); RequestBody fileBody = RequestBody.create(MediaType.parse("application/octet-stream"), file); RequestBody requestBody = new MultipartBuilder() .type(MultipartBuilder.FORM) .addPart(Headers.of( "Content-Disposition", "form-data; name=\"username\""), RequestBody.create(null, "qianfeng")) .addPart(Headers.of( "Content-Disposition", "form-data; name=\"mFile\"; filename=\"wjd.mp4\""), fileBody) .build(); Request request = new Request.Builder() .url("http://192.168.1.88:8080/App/upload") .post(requestBody) .build(); Call call = mOkHttpClient.newCall(request); call.enqueue(new Callback() { //... });
取消一个Call
取消单个
Call.cancel()
高级使用
响应缓存
设置缓存
OkHttp默认没有开启,
核心代码
private final OkHttpClient client; public CacheResponse(File cacheDirectory) throws Exception { int cacheSize = 10 * 1024 * 1024; // 10 MiB Cache cache = new Cache(cacheDirectory, cacheSize); client = new OkHttpClient(); client.setCache(cache); }
注意:一个缓存目录同时拥有多个缓存访问是错误的。大多数程序只需要调用一次new OkHttp(),在第一次调用时配置好缓存,然后其他地方只需要调用这个实例就可以了。否则两个缓存示例互相干扰,破坏响应缓存,而且有可能会导致程序崩溃。 响应缓存使用HTTP头作为配置。你可以在请求头中添加Cache-Control: max-stale=3600 ,OkHttp缓存会支持。你的服务通过响应头确定响应缓存多长时间,例如使用Cache-Control: max-age=9600。
设置缓存失效时间
缓存是由HTTP消息头中的”Cache-control”来控制的,常见的取值有private、no-cache、max-age、must-revalidate等,默认为private
max-age=xxx (xxx is numeric) 缓存的内容将在 xxx 秒后失效, 这个选项只在HTTP 1.1可用, 并如果和Last-Modified一起使用时, 优先级较高
must-revalidation/proxy-revalidation 如果缓存的内容失效,请求必须发送到服务器/代理以进行重新验证
no-cache 所有内容都不会被缓存
no-store 所有内容都不会被缓存到缓存或 Internet 临时文件中
private 内容只缓存到私有缓存中
public 所有内容都将被缓存
Interceptors(拦截)
概念
观察,修改以及可能短路的请求输出和响应请求的回来,通常情况下拦截器用来添加,移除或者转换请求或者回应的头部信息。 拦截器接口中有intercept(Chain chain)方法,同时返回Response
class LoggingInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); long t1 = System.nanoTime(); logger.info(String.format("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers())); Response response = chain.proceed(request); long t2 = System.nanoTime(); logger.info(String.format("Received response for %s in %.1fms%n%s", response.request().url(), (t2 - t1) / 1e6d, response.headers())); return response; } }
Application Interceptors
1.不必担心中间的responses,例如重定向和重连。
2.总是调用一次,即使是从缓存HTTP响应。
3.观察应用程序的原始意图。不关心OkHttp的注入headers,例如If-None-Match
4.允许短路和不执行Chain.proceed()
5.允许重连,多次调用proceed()
Network Interceptors
1.能够操作中间反应,例如重定向和重连。
2.不能被缓存响应,例如短路网络调用。
3.观察数据,正如它将在网络上传输
4.有权使用携带request的Connection
其他
源码地址:https://github.com/square/okhttp
官方介绍:http://square.github.io/okhttp/
API:http://square.github.io/okhttp/3.x/okhttp/
原理解析扩展
总体设计
结构图
说明
OKHttp总体设计图,主要是通过Diapatcher不断从RequestQueue中取出请求(Call),根据是否已缓存调用Cache或Network这两类数据获取接口之一,从内存缓存或是服务器取得请求的数据。该引擎有同步和异步请求,同步请求通过Call.execute()直接返回当前的Response,而异步请求会把当前的请求Call.enqueue添加(AsyncCall)到请求队列中,并通过回调(Callback)的方式来获取最后结果
OKHttp中重要的类
OkHttpClient 核心类创建一个http请求
Dispatcher
当一个异步请求被执行,每个调度程序使用一个ExecutorService运行在内部调用
Deque<runningAsyncCalls>:正在运行的任务,仅仅是用来引用正在运行的任务以判断并发量,注意它并不是消费者缓存
Deque<readyAsyncCalls>:缓存(用数组实现,可自动扩容,无大小限制)
ExecutorService:消费者池(也就是线程池)
AsyncCall: 队列中需要处理的Runnable(包装了异步回调接口)
Dispatcher: 分发者,也就是生产者(默认在主线程)
maxRequestsPerHost = 5: 每个主机最大请求数为5
maxRequests = 64: 最大并发请求数为64
Route 地址封装类
Platform版本适配类,针对Android2.3到6.0后的网络请求的适配支持
Connnection 连接类主要支持HTTP, HTTPS, HTTPS+SPDY
ConnnectionPool连接池的管理类
Request 一个http请求
Response 一个http响应
Call一个请求响应的接口回调
请求流程
结构图
类关系图
结构图
设计模式之Builder
概念
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示.
Builder模式是一步一步创建一个复杂的对象,它允许用户可以只通过指定复杂对象的类型和内容就可以构建它们.用户不知道内部的具体构建细节.Builder模式是非常类似抽象工厂模式
为何使用?
是为了将构建复杂对象的过程和它的部件解耦.注意: 是解耦过程和部件
因为一个复杂的对象,不但有很多大量组成部分,如汽车,有很多部件:车轮 方向盘 发动机还有各种小零件等等,部件很多,但远不止这些,如何将这些部件装配成一辆汽车,这个装配过程也很复杂(需要很好的组装技术),Builder模式就是为了将部件和组装过程分开
第三方框架
OkHttpFinal
简介
一个对OkHttp封装的简单易用型HTTP请求和文件下载管理框架
功能
简化OkHttp使用
支持GET,POST,PUT,DELETE,HEAD,PATCH谓词
支持Activity和Fragment生命周期结束后终止请求
支持Download Manager功能
支持文件下载多事件回调
支持返回bean对象
支持返回json String数据
支持返回JsonObject对象
支持https请求
支持https证书访问
支持文件上传
支持全局params
支持全局header
支持http cancel
使用
第一步导包
Studio
compile 'cn.finalteam:okhttpfinal:2.0.3' #带下载管理 compile 'cn.finalteam:okhttpfinal-dm:2.0.3'
Eclipse
https://github.com/pengjianbo/OkHttpFinal/tree/master/downloads 下载jar
初始化框架
在你App Application中初始化OkHttpFinal(此初始化只是简单赋值不会阻塞线程)
OkHttpFinalConfiguration.Builder builder = new OkHttpFinalConfiguration.Builder(); OkHttpFinal.getInstance().init(builder.build());
常用方法
GET请求
POST请求
文件上传
文件下载
混淆
#--------------- BEGIN: okhttp ---------- -keepattributes Signature -keepattributes *Annotation* -keep class com.squareup.okhttp.** { *; } -keep interface com.squareup.okhttp.** { *; } -dontwarn com.squareup.okhttp.** #--------------- END: okhttp ---------- #--------------- BEGIN: okio ---------- -keep class sun.misc.Unsafe { *; } -dontwarn java.nio.file.* -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement -dontwarn okio.** #--------------- END: okio ----------
地址
github地址:https://github.com/pengjianbo/OkHttpFinal/wiki
调试工具
Stetho
使用
必要条件
3、必须使用Chrome浏览器不需要安装任何插件只需在浏览器输入chrome://inspect 就行
2、集成FaceBook的Stetho调试框架 还有很多其他的功能额,自己尝试
1、首先必须是以OkHttp作为请求框架 Stetho也是利用了OkHttp强大的拦截器的功能
集成Stetho
android Studio只需要添加以下几行依赖就行了 compile 'com.facebook.stetho:stetho:1.3.1'
在你的Application中添加
public class MyApplication extends Application { public void onCreate() { super.onCreate(); Stetho.initializeWithDefaults(this); } }
OkHttp添加拦截器
new OkHttpClient.Builder() .addNetworkInterceptor(new StethoInterceptor()) .build()
打开你的Chrome浏览器在地址栏输入chrome://inspect
https://github.com/facebook/stetho
开发注意事项
回调方法不会自动到主线程中运行
不能在主线程(UI主线程)发起网络;
UI操作应该在UI线程中执行;
所以,以下代码会导致异常:NetworkOnMainThreadException,因为不满足原则1。 //不能在主线程中执行这段代码 OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder().url("").build(); client.newCall(request).enqueue(new Callback() { @Override public void onResponse(Call call, Response response) throws IOException { Toast.makeText(this,"Success",Toast.LENGTH_SHORT).show(); } @Override public void onFailure(Call call, IOException e) { } });
Cookie
cookies是一种WEB服务器通过浏览器在访问者的硬盘上存储信息的手段
OkHttp 3.0开始,默认不保存Cookie,要自己使用CookieJar来保存Cookie
使用Builder来构建OkHttpClient才能设置CookieJar
自动管理Cookie
OkHttpClient client = new OkHttpClient.Builder() .cookieJar(new CookieJar() { private final HashMap<HttpUrl, List<Cookie>> cookieStore = new HashMap<>(); @Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) { cookieStore.put(url.host(), cookies); } @Override public List<Cookie> loadForRequest(HttpUrl url) { List<Cookie> cookies = cookieStore.get(url.host()); return cookies != null ? cookies : new ArrayList<Cookie>(); } }) .build();
第33天
ButterKnife
基础知识
为什么要用注入
应用程序的流程取决于在程序运行时对象图的建立。通过抽象定义的对象交互可以实现这样的动态流程。而使用依赖注入技术或者服务定位器便可以完成运行时绑定
核心类功能介绍
1. 反射
1. 定义
1.1. 在运行状态中, 对于任意一个类,都能够知道这个类的所有属性和方法; 对于任意一个对象,都能够调用它的任意一个方法和属性; 这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制
2. 功能
1. 在运行时判断任意一个对象所属的类
2. 在运行时构造任意一个类的对象
3. 在运行时判断任意一个类所具有的成员变量和方法
4. 在运行时调用任意一个对象的方法;生成动态代理
3. 使用
1. 首先我们想通过反射获取类或者接口的属性和方法, 就必须获得这个类或者接口的Class对象
1. 使用 使用 Class 类的静态方法 Class.forName (String name)
2. 类静态的语法 , Stirng.class
3. 使用类的实例化的 getClass 方法 obj.getClass ()
2. 获取类的构造器
1. public Constructor <?>[] getConstructors () 返回类中所有的 public 构造器集合,默认构造器的下标为 0
2. public Constructor < T > getConstructor ( Class <?>... parameterTypes ) 返回指定 public 构造器,参数为构造器参数类型 集合
3. public Constructor <?>[] getDeclaredConstructors () 返回类中所有的构造器,包括私有
4. public Constructor < T > getDeclaredConstructor ( Class <?>... parameterTypes ) 返回任意指定的构造 器
5. 重要方法
5.1. User user = class.newInstant();
5.2. 必须有默认的构造方法
3. 获取类的成员变量(Field)
1. public Field getDeclaredField ( String name) 获取任意指定名字的成员变量
2. public Field [] getDeclaredFields () 获取所有的成员变量
3. public Field getField ( String name) 获取任意 public 成员变量
4. public Field [] getFields () 获取所有的 public 成员变量
5. 重要方法
1. Field 重要方法set(Object obj, Object value) 字段设置为指定的新值
2. 如果属性不是public修饰必须设置field.setAccessible(boolean flag)
4. 获取类的方法(Method)
1. public Method getDeclaredMethod ( String name, Class <?>... parameterTypes ) 获取任意指定方法
2. public Method [] getDeclaredMethods () 获取所有的方法
3. public Method [] getMethods () 获取所有的共有方法的集合
4. public Method getMethod ( String name, Class <?>... parameterTypes ) 获取指定公有方法
5. 重要方法
5.1. Method 重要方法invoke(Object obj ,Object…parmasType);
5.2. 如果属性不是public修饰必须设置method.setAccessible(boolean flag)
5. 核心代码
5.1. public static void main(String[] args) throws Exception { Class<?> cls = null; // User user = new User(); // cls = Class.forName("com.qianfeng.demo.bean.User"); cls = User.class; // cls = user.getClass(); //公开的构造指定参数的构造器 // Constructor constructor = cls.getConstructor(new Class[]{String.class}); // User user = (User) constructor.newInstance(new Object[]{"1"}); // System.out.println(user.toString()); //实例化对象 User user = (User) cls.newInstance(); // System.out.println(user.toString()); // System.out.println(cls.getName()); Field field = null; // 返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。 // field= cls.getField("id"); field = cls.getDeclaredField("name"); //获得所有的声明的成员变量 // Field[] fields = cls.getDeclaredFields(); // for (Field field1 : fields) { // System.out.println(field1.getName()); // } //暴力破解 //取消 Java 语言访问检查 field.setAccessible(true); //给数次那个赋值 field.set(user,"老罗卖手机"); // System.out.println(field.getName()); // // Method method = cls.getMethod("show",new Class[]{String.class}); // method.invoke(user,new Object[]{"大叔我们不约!!!"}); // Method method = cls.getDeclaredMethod("test",new Class[]{}); // method.setAccessible(true); // method.invoke(user,new Object[]{}); Method[] methods = cls.getDeclaredMethods(); for (Method method1 : methods){ System.out.println(method1.getName()); } // System.out.println(user.toString()); }
2. 注解(Annotation)
1. 定义
1.1. 注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK 1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释
2. 作用
2.1. 标记作用,用于告诉编译器一些信息让编译器能够实现基本的编译检查,如@Override、Deprecated
2.2. 运行时动态处理,获得注解信息配合反射使用
2.2.1. 流程图
2.2.1.1.
2.3. 编译时动态处理,动态生成代码,如Butter Knife、Dagger 2
2.3.1. 配合注解处理器
2.3.1.1. http://blog.csdn.net/duo2005duo/article/details/50541281
2.3.1.2. http://www.jianshu.com/p/2494825183c5
2.3.1.3. http://www.jianshu.com/p/e88b482abd37?utm_campaign=haruki&utm_content=note&utm_medium=reader_share&utm_source=qq
3. 分类
1. 基本内置注解
1. @Override作用:通过代码里标识的元数据让编译器能过实现基本的编译检查
2. @Deprecated作用:是对不应该再使用的方法添加注解,当编程人员使用这些方法时,将会在编译时显示提示信息
3. @SuppressWarning-->参数
1. deprecation,使用了过时的类或方法时的警告
2. unchecked,执行了未检查的转换时的警告
3. fallthrough,当 Switch 程序块直接通往下一种情况而没有 Break 时的警告
4. path,在类路径、源文件路径等中有不存在的路径时的警告
5. serial,当在可序列化的类上缺少serialVersionUID 定义时的警告
6. finally ,任何 finally 子句不能正常完成时的警告
7. all,关于以上所有情况的警告
2. 元注解
1. 修饰注解的注解叫元注解
2. 四种元注解
1. @Target-->值
1. 默认值为任何元素,表示该注解用于什么地方。可用的ElementType参数
2. ElementType.FIELD: 成员变量、对象、属性(包括enum实例)
3. ElementType.METHOD: 方法声明
4. ElementType.TYPE: 类、接口(包括注解类型)或enum声明
5. ElementType.LOCAL_VARIABLE: 局部变量声明
6. ElementType.PACKAGE: 包声明
7. ElementType.PARAMETER: 参数声明
2. @Retention-->值
2.1. 表示需要在什么级别保存该注释信息,可选的RetentionPoicy参数包括
2.2. RetentionPolicy.SOURCE: 保留在java源文件,编译器被丢掉
2.3. RetentionPolicy.CLASS:保留在class文件中,但会被VM丢弃(默认)
2.4. RetentionPolicy.RUNTIME:内存中的字节码,VM将在运行时也保留注解,因此可以通过反射机制读取注解的信息
3. @Documented将注解包含在JavaDoc中
4. @Inherited允许子类继承父类中的注解
3. 自定义注解
1. 通过 @interface 定义,注解名即为自定义注解名
2. 注解配置参数名为注解类的方法名
1. 所有方法没有方法体,没有参数没有修饰符,实际只允许public & abstract 修饰符,默认为 public ,不允许抛异常
2. 方法返回值只能是基本类型,String, Class, annotation, enumeration 或者是他们的一维数组
3. 如果方法名是value();在使用的时候可以省略不写,其他一律key=value的形式出现
3. 可以加 default 表示默认值
4. 根据作用域
4.1. 1、源码时注解(RetentionPolicy.SOURCE)
4.2. 2、编译时注解(RetentionPolicy.CLASS)
4.3. 3、运行时注解(RetentionPolicy.RUNTIME)
4. 案例
1. 定义
1.1. @Retention(value = RetentionPolicy.RUNTIME) @Target(value = ElementType.FIELD) @Documented @Inherited public @interface MyAnotation { int id() default 0; String name() default "罗总监"; String[] values() default {"","",""}; Color color(); }
2. 使用
2.1. public class User { @MyAnotation(id = 0,name="zhangwei",values = {"1111","2222","333333"},color = Color.Green) private String name; private String id; public String getName() { return name; } public void setName( String name) { int dex = 0; this.name = name; } public String getId() { return id; } public void setId(String id) { this.id = id; } }
3. 解析
3.1. public static void main(String[] args) { Class<?> cls = User.class; Field[] fields = cls.getDeclaredFields(); for (Field f : fields) { MyAnotation annotation = f.getAnnotation(MyAnotation.class); if (annotation != null) { int id = annotation.id(); System.out.println(id); String[] values = annotation.values(); System.out.println(values.toString()); Color color = annotation.color(); System.out.println(color.toString()); } } }
3. 代理
1. 在某些情况下,我们不希望或是不能直接访问对象 A,而是通过访问一个中介对象 B,由 B 去访问 A 达成目的,这种方式我们就称为代理。 这里对象 A 所属类我们称为委托类,也称为被代理类,对象 B 所属类称为代理类。
2. 静态代理
1. 简介: 代理类在程序运行前已经存在的代理方式称为静态代理
2. 代码
2.1. class ClassA { public void method1() {}; public void method2() {}; public void method3() {}; } public class ClassB { private ClassA a; public ClassB(ClassA a) { this.a = a; } public void method1() { a.method1(); }; public void method2() { a.method2(); }; // not export method3() }
3. 静态代理中代理类和委托类也常常继承同一父类或实现同一接口
4. 解释:上面ClassA是委托类,ClassB是代理类,ClassB中的函数都是直接调用ClassA相应函数,并且隐藏了Class的method3()函数
3. 动态代理
1. 简介: 代理类在程序运行前不存在、运行时由程序动态生成的代理方式称为动态代理 Java 提供了动态代理的实现方式,可以在运行时刻动态生成代理类。这种代理方式的一大好处是可以方便对代理类的函数做统一或特殊处理,如记录所有函数执行时间、所有函数执行前添加验证判断、对某个特殊函数进行特殊操作,而不用像静态代理方式那样需要修改每个函数
2. 实现动态代理包括三步
1. 新建委托类;
2. 实现InvocationHandler接口,这是负责连接代理类和委托类的中间类必须实现的接口;
3. 通过Proxy类新建代理类对象;
4. 代理优点
4.1. 隐藏委托类的实现
4.2. 解耦,不改变委托类代码情况下做一些额外处理,比如添加初始判断及其他公共操作 根据程序运行前代理类是否已经存在,可以将代理分为静态代理和动态代理。
简介
ButterKnife 是一个 Android 系统的 View 注入框架,能够通过『注解』的方式来绑定 View 的属性或方法,这个开源库可以让我们从大量的findViewById()和setOnclicktListener()解放出来,其对性能的影响微乎其微(查看过Butter Knife的源码,其自定义注解的实现都是限定为RetentionPolicy.CLASS,也就是到编译出.class文件为止有效,在运行时不额外消耗性能,其是通过java注解自动生成java代码的形式来完成工作)
作用
Field and method binding for Android views which uses annotation processing to generate boilerplate code for you.
功能
1. 通过使用 @BindView 来消除 findViewById
2. 将多个 View 组织到一个列表中,一次性操作它们
3. 通过使用 @onClick 为 View 绑定监听,消除 listener 的匿名内部类
4. 通过使用资源注解如 @BindColor,来消除资源的查找
配置
1.在project的build.gradle中 buildscript { repositories { mavenCentral() } dependencies { //添加注解编译器 apt classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' } } 2.在moudle中添加 apply plugin: 'com.neenbedankt.android-apt' dependencies { compile 'com.jakewharton:butterknife:8.4.0' apt 'com.jakewharton:butterknife-compiler:8.4.0' }
使用
在Acitvity中使用
public void bind(ExampleActivity activity) { activity.subtitle = (android.widget.TextView) activity.findViewById(2130968578); activity.footer = (android.widget.TextView) activity.findViewById(2130968579); activity.title = (android.widget.TextView) activity.findViewById(2130968577); }
class ExampleActivity extends Activity { @BindView(R.id.title) TextView title; @BindView(R.id.subtitle) TextView subtitle; @BindView(R.id.footer) TextView footer; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.bind(this); // TODO Use fields... } }
在没有Activity中使用
在Fragment中
public class FancyFragment extends Fragment { @BindView(R.id.button1) Button button1; @BindView(R.id.button2) Button button2; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fancy_fragment, container, false); ButterKnife.bind(this, view); // TODO Use fields... return view; } }
在Adapter中
ublic class MyAdapter extends BaseAdapter { @Override public View getView(int position, View view, ViewGroup parent) { ViewHolder holder; if (view != null) { holder = (ViewHolder) view.getTag(); } else { view = inflater.inflate(R.layout.whatever, parent, false); holder = new ViewHolder(view); view.setTag(holder); } holder.name.setText("John Doe"); // etc... return view; } static class ViewHolder { @BindView(R.id.title) TextView name; @BindView(R.id.job_title) TextView jobTitle; public ViewHolder(View view) { ButterKnife.bind(this, view); } } }
事件注入
带View参数
@OnClick(R.id.submit) public void submit(View view) { // TODO submit data to server... }
不带View参数
@OnClick(R.id.submit) public void submit() { // TODO submit data to server... }
同时注入多个View事件
@OnClick({ R.id.text1, R.id.btn, R.id.iv }) public void pickDoor(View view) { switch (view.getId()){ case R.id.text1: // TODO: break; case R.id.btn: // TODO: break; case R.id.iv: // TODO: break; } }
资源绑定
class ExampleActivity extends Activity { @BindString(R.string.title) String title; @BindDrawable(R.drawable.graphic) Drawable graphic; @BindColor(R.color.red) int red; // int or ColorStateList field @BindDimen(R.dimen.spacer) Float spacer; // int (for pixel size) or float (for exact value) field // ... }
第32天
Glide
简介
Glide是一个快速高效开源的媒体管理的框架,包括图片加载,内存管理,磁盘管理,使用对象池管理,简单易用的库
对象池的主要目的是通过减少大对象的分配以重用来提高性能
Glide支持获取、解码和显示视频图像和动画GIF,Glide包涵了一个灵活的接口,允许开发人员插入到几乎所有的网络框架,Glide默认使用了一个自定义HttpUrlconnect,你也可以使用Google的Volley或者Square的okhttp代替
Api地址: http://bumptech.github.io/glide/javadocs/latest/index.html
Github地址: https://github.com/bumptech/glide
使用介绍
导入包
Eclipse 下载jar
https://github.com/bumptech/glide/releases
AS
compile 'com.github.bumptech.glide:glide:3.+'
加载图片
Glide.with(this) .load(imageUrl) .into(imgView);
Glide.with
Glide.with(android.app.Activity)
Glide.with(android.support.v4.app.FragmentActivity)
Glide.with(android.app.Fragment)
Glide.with(android.support.v4.app.Fragment)
Glide.with(Context)
同时将Activity/Fragment作为with()参数的好处是:图片加载会和Activity/Fragment的生命周期保持一致,比如Paused状态在暂停加载,在Resumed的时候又自动重新加载。所以我建议传参的时候传递Activity 和 Fragment给Glide,而不是Context
load
placeholder(loading占位图)
error(加载出错占位图)
crossFade(淡入淡出动画)默认动画时间300毫秒 如果想修改时间可crossFade(int duration)
dontAnimate不使用动画
override调整图片大小
fitCenter 缩放图像让图像都测量出来等于或小于 ImageView 的边界范围。该图像将会完全显示,但可能不会填满整个 ImageView
centerCrop 缩放图像让它填充到 ImageView 可能会完全填充,但图像可能不会完整显示。
thumbnail
范围0-1.0f
into
加载gif
缓存
示例代码
内存缓存
默认开启内存缓存
磁盘缓存
Glide 缓存了原始图像,全分辨率图像和另外小版本的图像。比如,如果你请求的一个图像是 5000x500 像素的,但你的 ImageView 是 250*300 像素的,3.6以后Glide 将会把250*300个尺寸都进行缓存。
自定义视图类
SimpleTarget
ViewTarget
GlideDrawableImageViewTarget
转化
在图片被显示之前,transformations(转换) 可以被用于图像的操作处理。比如,如果你的应用需要显示一个灰色的图像,但是我们只能访问到原始色彩的版本,你可以用 transformation 去操作 bitmap,从而将一个明亮色彩版本的图片转换成灰暗的版本。当然不仅限于颜色转换。你可以图片的任意属性:尺寸,范围,颜色,像素位置等等!
compile 'jp.wasabeef:glide-transformations:2.0.0' compile 'jp.co.cyberagent.android.gpuimage:gpuimage-library:1.4.0'
注意:当你用了转换后你就不能使用 .centerCrop() 或 .fitCenter()
全局配置
整体
设置图片保存格式
设置磁盘缓存
设置内存缓存
建议在推荐上面进行修改 例如增加20%
int customMemorySize = (int) (1.2 * memorySizeCalculator.getMemoryCacheSize()); int customBitmaPoolSize = (int) (1.2 * memorySizeCalculator.getBitmapPoolSize()); builder.setMemoryCache(new LruResourceCache(customMemorySize)); builder.setBitmapPool(new LruBitmapPool(customBitmaPoolSize));
集成网络栈
OkHttp
导包
compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar' 支持okhttp3.x compile 'com.squareup.okhttp3:okhttp:3.2.+'
compile 'com.github.bumptech.glide:okhttp-integration:1.4.0@aar' 支持Okhttp2.x compile 'com.squareup.okhttp:okhttp:2.6.0'
注册
Volley
导包
compile 'com.github.bumptech.glide:volley-integration:1.4.0@aar'
compile 'com.mcxiaoke.volley:library:1.0.+'
注册
混淆
-keep public class * implements com.bumptech.glide.module.GlideModule -keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { **[] $VALUES; public *; }
使用注意事项
ImageView的scaleType设置不当, 导致使用Glide时出现OOM
ImageView的scaleType的问题,当设置为fitXY时,虽然ImageView显示那么点尺寸,但是,但是Glide加载图片时,却是以全分辨率加载的,于是加载几张,就OOM了。改成fitCenter或者centerCrop(试了一下fitStart、fitEnd也行,总之看需求了),就好了,会自动缓存小图,滚动起来也非常流畅。
其他
一个基于Glide的transformation库, 拥有裁剪,着色,模糊,滤镜等多种转换效果
https://github.com/wasabeef/glide-transformations
Glide加载时很方便使用Palette的库
2.GlidePalette(https://github.com/florent37/GlidePalette)
官方使用介绍
https://github.com/bumptech/glide/wiki
开发版本
https://github.com/bumptech/glide/wiki/Snapshots
Glide与Picasso对比
http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0327/2650.html
第34天
WebView
1. 简介
1.1. 为了方便开发者实现在app内展示网页并与网页交互的需求,Android SDK提供了WebView组件。 它继承自AbsoluteLayout,展示网页的同时,也可以在其中放入其他的子View。 现如今,Hybrid应用似乎占据的APP的主流类型,那么关于WebView的使用就变得越发的重要。 从Android 4.4(KitKat)开始,原本基于WebKit的WebView开始基于Chromium内核,这一改动大大提升了WebView组件的性能以及对HTML5,CSS3,JavaScript的支持。不过它的API却没有很大的改动,在兼容低版本的同时只引进了少部分新的API,并不需要你做很大的改动
2. 内核
1. 浏览器的内核引擎,基本上是四分天下:
2. Trident: IE 以Trident 作为内核引擎;
3. Gecko: Firefox 是基于 Gecko 开发;
4. WebKit: Safari, Google Chrome,傲游3,猎豹浏览器,百度浏览器 opera浏览器 基于 Webkit 开发。
5. Presto: Opera的内核,但由于市场选择问题,主要应用在手机平台--Opera mini
3. 基础使用
3.1. 在布局文件中声明
3.1.1.
3.2. 在代码中设置
3.2.1.
3.3. 在AndroidManifest添加权限
3.3.1.
4. 核心类
1. WebView
1. webView.loadUrl 直接加载网页、图片并显示.(本地或是网络上的网页、图片、gif)
2. LoadData 显示文字与图片内容
3. LoadDataWithBase 显示文字与图片内容
4. webView.reload(); //重新刷新当前url
5. webView.canGoBack(); //判断是否有回退历史记录
6. webView.canGoForward(); //判断是否有向前的历史记录
7. webView.goBack(); //在webView的历史记录后退到下一项
8. webView.goForward(); //在webView的历史记录前进到上一页
9. webView.clearCache(false); //清空缓存 false表示清空内存中的资源缓存 不清空磁盘的缓存 true 全部清除
10. webView.clearHistory(); //清除历史记录
11. webView.clearFormData(); //清除自定完成填充的表单数据
12. webView.onPause(); //当页面被失去焦点被切换到后台不可见状态,需要执行onPause操作,该操作会通知内核安全地暂停所有动作,比如动画的执行或定位的获取等。需要注意的是该方法并不会暂停JavaScript的执行,若要暂停JavaScript的执行请使用接下来的这个方法。
13. webView.onResume(); //:在先前调用onPause()后,我们可以调用该方法来恢复WebView的运行。
14. webView.destroy(); //销毁WebView。需要注意的是:这个方法的调用应在WebView从父容器中被remove掉之后。我们可以手动地调用
2. WebSettings
1. setJavaScriptEnabled(true); //支持js
2. setPluginsEnabled(true); //支持插件
3. setUseWideViewPort(false); //将图片调整到适合webview的大小
4. setSupportZoom(true); //支持缩放
5. setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN); //支持内容重新布局
6. supportMultipleWindows(); //多窗口
7. setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //关闭webview中缓存
1. cache模式有如下几种:
2. LOAD_DEFAULT: 如果我们应用程序没有设置任何cachemode, 这个是默认的cache方式。 加载一张网页会检查是否有cache,如果有并且没有过期则使用本地cache,否则 从网络上获取。
3. LOAD_CACHE_ELSE_NETWORK: 使用cache资源,即使过期了也使用,如果没有cache才从网络上获取。
4. LOAD_NO_CACHE: 不使用cache 全部从网络上获取
5. LOAD_CACHE_ONLY: 只使用cache上的内容
8. setAllowFileAccess(true); //设置可以访问文件
9. setNeedInitialFocus(true); //当webview调用requestFocus时为webview设置节点
10. webview webSettings.setBuiltInZoomControls(true); //设置支持缩放
11. setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口
12. setLoadWithOverviewMode(true); // 缩放至屏幕的大小
13. setLoadsImagesAutomatically(true); //支持自动加载图片
3. WebViewClient
1. doUpdateVisitedHistory(WebView view, String url, boolean isReload) //(更新历史记录)
2. onFormResubmission(WebView view, Message dontResend, Message resend) //(应用程序重新请求网页数据)
3. onLoadResource(WebView view, String url) // 在加载页面资源时会调用,每一个资源(比如图片)的加载都会调用一次。
4. onPageStarted(WebView view, String url, Bitmap favicon) //这个事件就是开始载入页面调用的,通常我们可以在这设定一个loading的页面,告诉用户程序在等待网络响应。
5. onPageFinished(WebView view, String url) //在页面加载结束时调用。同样道理,我们知道一个页面载入完成,于是我们可以关闭loading 条,切换程序动作。
6. onReceivedError(WebView view, int errorCode, String description, String failingUrl)// (报告错误信息)
7. onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host,String realm)//(获取返回信息授权请求)
8. onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) //重写此方法可以让webview处理https请求。
9. onScaleChanged(WebView view, float oldScale, float newScale) // (WebView发生改变时调用)
10. shouldOverrideKeyEvent(WebView view, KeyEvent event)//重写此方法才能够处理在浏览器中的按键事件。
11. shouldOverrideUrlLoading(WebView view, String url) //在点击请求的是链接是才会调用,重写此方法返回true表明点击网页里面的链接还是在当前的webview里跳转,不跳到浏览器那边。这个函数我们可以做很多操作,比如我们读取到某些特殊的URL,于是就可以不打开地址,取消这个操作,进行预先定义的其他操作,这对一个程序是非常必要的。
5. 开发常用优化
1. 加快HTML网页装载完成的速度 同时在WebView的WebViewClient实例中的onPageFinished()方法添加如下代码:
1. public void init () { if(Build.VERSION.SDK_INT >= 19) { webView.getSettings().setLoadsImagesAutomatically(true); } else { webView.getSettings().setLoadsImagesAutomatically(false); } }
2. @Override public void onPageFinished(WebView view, String url) { if(!webView.getSettings().getLoadsImagesAutomatically()) { webView.getSettings().setLoadsImagesAutomatically(true); } }
2. 是否存在滚动条
1. 当我们做类似上拉加载下一页这样的功能的时候,页面初始的时候需要知道当前WebView是否存在纵向滚动条,如果有则不加载下一页,如果没有则加载下一页直到其出现纵向滚动条。首先继承WebView类,在子类添加下面的代码
2. public boolean existVerticalScrollbar () { return computeVerticalScrollRange() > computeVerticalScrollExtent(); }
3. 是否已滚动到页面底部
1. 同样我们在做上拉加载下一页这样的功能时,也需要知道当前页面滚动条所处的状态,如果快到底部,则要发起网络请求数据更新网页。同样继承WebView类,在子类覆盖onScrollChanged方法
2. @Override protected void onScrollChanged(int newX, int newY, int oldX, int oldY) { super.onScrollChanged(newX, newY, oldX, oldY); if (newY != oldY) { float contentHeight = getContentHeight() * getScale(); // 当前内容高度下从未触发过, 浏览器存在滚动条且滑动到将抵底部位置 if (mCurrContentHeight != contentHeight && newY > 0 && contentHeight <= newY + getHeight() + mThreshold) { // TODO Something... mCurrContentHeight = contentHeight; } } }
4. ViewPager里非首屏WebView点击事件不响应
1. 如果你的多个WebView是放在ViewPager里一个个加载出来的,那么就会遇到这样的问题。ViewPager首屏WebView的创建是在前台,点击时没有问题;而其他非首屏的WebView是在后台创建,滑动到它后点击页面会出现如下错误日志
2. 解决这个问题的办法是继承WebView类,在子类覆盖onTouchEvent方法
3. @Override public boolean onTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onScrollChanged(getScrollX(), getScrollY(), getScrollX(), getScrollY()); } return super.onTouchEvent(ev); }
5. WebView硬件加速导致页面渲染闪烁
5.1. 4.0以上的系统我们开启硬件加速后,WebView渲染页面更加快速,拖动也更加顺滑。但有个副作用就是,当WebView视图被整体遮住一块,然后突然恢复时(比如使用SlideMenu将WebView从侧边滑出来时),这个过渡期会出现白块同时界面闪烁。解决这个问题的方法是在过渡期前将WebView的硬件加速临时关闭,过渡期后再开启
6. WebView与上层父元素的TouchMove事件冲突
7. WebView解决中文乱码
7.1. webView.getSettings().setDefaultTextEncodingName("UTF -8");//设置默认为utf-8
7.2. webView.loadData(htmlData, "text/html", "UTF -8");//API提供的标准用法,无法解决乱码问题
7.3. webView.loadData(htmlData, "text/html; charset=UTF-8", null);//这种写法可以正确解码
第35天
版本控制