学习目标
熟练掌握常用开源UI类库的使用
PullToRefresh:强大的下拉刷新库,作者是ChrisBane(可选)
PinnedHeaderListView:优秀的条目分割顶部固定效果
PhotoView:强大的图片跟随手势进行缩放移动的类库,作者是ChrisBane
CustomShapeImageView:强大的自定义形状的ImageView
MPAndroidChart:强大的图表用于数据统计展示的类库(可选)
PullToRefresh
特点:无侵入的下拉刷新,就是可用在ListView,ScrollView,GridView,ViewPager等所有能滑动的控件上,而且扩展性强,可以监听下拉进度,更改下拉和上拉的布局以及动画,github地址:https://github.com/chrisbanes/Android-PullToRefresh,其作者是Google官方Android工程师ChrisBane
原理:
PullToRefreshBase本身继承LinearLayout,在构造方法中add3个View,分别是刷新头布局,刷新脚布局,中间是refreshableView(由子类实现,子类实现的有ListView,GridView,ScrollView等所有可以滑动的布局);
在onInterceptTouchEvent方法中去判断是否应该拦截以及拖拽的方向,并通过isReadyForPull()方法判断是要拉出刷新布局还是让refreshableView本身滚动,该方法是抽象方法,由子类实现;
在onTouchEvent方法中获取手指移动距离,并通过scrollTo方法滚动出刷新布局;
将刷新布局抽象为LoadingLayout,并接收当前刷新布局滚动的比例,用以给上拉和下拉刷新动画提供接口,其目前实现类有RotateLoadingLayout和FlipLoadingLayout,即旋转箭头和翻转箭头效果,默认是使用RotateLoadingLayout作为刷新动画效果;
用法如下,此处以PullToRefreshListView举例:
refreshListView = (PullToRefreshListView) View.inflate(this, R.layout.activity_ptr_listview, null);
//1.设置刷新模式,可选有:只上拉,只下拉,两边都可以拉,禁用刷新(即都不可以拉)
refreshListView.setMode(Mode.BOTH);//设置两边都可以拉
setContentView(refreshListView);
for (int i = 0; i < 15; i++) {
list.add(i+"碗牛肉面");
}
//2.获取refreshableView来设置数据,此处的实现是ListView
ListView listView = refreshListView.getRefreshableView();
adapter = new MyAdapter(this, list);
listView.setAdapter(adapter);
//3.设置刷新监听
refreshListView.setOnRefreshListener(new OnRefreshListener<ListView>() {
@Override
public void onRefresh(PullToRefreshBase<ListView> refreshView) {
requestData(refreshListView.getCurrentMode()==Mode.PULL_FROM_END);
}
});
//4.设置滚动到最后一个条目的监听
refreshListView.setOnLastItemVisibleListener(new OnLastItemVisibleListener() {
@Override
public void onLastItemVisible() {
//do something
}
});
pinned-section-listview
特点:让ListView的指定条目在滑动到顶部的时候固定在头部,github地址:https://github.com/beworker/pinned-section-listview
原理:
定义接口PinnedSectionListAdapter继承ListAdapter,并增加isItemViewTypePinned方法来判断当前是否是Section(即固定的选项);
我们所写的adapter要实现PinnedSectionListAdapter,并且按照自己的逻辑实现isItemViewTypePinned方法;
定义PinnedSectionListView继承自ListView,设置onScrollListener,在onScroll方法中去判断当前firstVisiableItem是否是Section,如果是则通过dispatchDraw方法在顶部位置绘制当前Section的View,正好覆盖着firstVisiableItem的View,看起来就像是固定在头部一样;
用法如下:
只需将自己的Adapter实现PinnedSectionListAdapter,并实现isItemViewTypePinned方法,如下:
class MyAdapter extends BaseAdapter implements PinnedSectionListAdapter{
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
//定义条目类型
private final int ITEM_NORMAL = 0;//普通数据条目
private final int ITEM_SECTION = 1;//需要固定的Section条目
/**
* 总共2种条目类型,一种是分类条目,一种是普通的数据条目
*/
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(int position) {
BaseBean baseBean = list.get(position);
if(baseBean instanceof Person){
return ITEM_NORMAL;
}else {
return ITEM_SECTION;
}
}
/**
* 此处借助于itemViewType来判断是否是需要固定的条目;
* 所以必须实现另外2个方法,就是getViewTypeCount和getItemViewType,
* 只有viewType是ITEM_SCTION的才需要固定
*/
@Override
public boolean isItemViewTypePinned(int viewType) {
return viewType==ITEM_SECTION;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView textView = new TextView(MainActivity.this);
textView.setTextColor(Color.DKGRAY);
textView.setPadding(15, 15, 15, 15);
//根据条目类型来返回条目View,并且设置对应的数据
int itemViewType = getItemViewType(position);
switch (itemViewType) {
case ITEM_NORMAL:
textView.setBackgroundColor(Color.WHITE);
Person person = (Person) list.get(position);
textView.setText(person.getName());
break;
case ITEM_SECTION:
textView.setBackgroundColor(Color.parseColor("#ff99cc00"));
SectionItem sectionItem = (SectionItem) list.get(position);
textView.setText(sectionItem.getSectionName());
break;
}
return textView;
}
}
数据的准备如下,将列表数据和分类数据都添加到同一个集合:
// 准备数据
// 添加小学生分类数据
list.add(new StuCategory("小学生"));
// 初始化小学生数据
for (int i = 0; i < 12; i++) {
list.add(new Stu("小学生 - " + i));
}
// 添加初中生分类数据
list.add(new StuCategory("初中生"));
// 初始化初中学生数据
for (int i = 0; i < 12; i++) {
list.add(new Stu("初中生 - " + i));
}
// 添加高中生分类数据
list.add(new StuCategory("高中生"));
// 初始化高中学生数据
for (int i = 0; i < 12; i++) {
list.add(new Stu("高中生 - " + i));
}
PhotoView
一个让图片随着收缩放大移动的类库,使用简单,缩放流畅.目前大部分app的缩放效果都是使用这个,github地址:https://github.com/chrisbanes/PhotoView,其作者是Google官方Android工程师ChrisBane
原理:
对ImageView添加OnTouchListener和GestureDecetor用来检查触摸移动的距离,以及双击事件,多点触摸的距离,
然后通过更改ImageView的Martrix来实现移动,缩放图片
用法如下:
单独使用ImageView的时候,使用PhotoView替换掉ImageView即可:
PhotoView mPhotoView = new PhotoView(this);
在ViewPager中使用的时候请使用HackyViewPager:
public class HackyViewPager extends ViewPager {
private boolean isLocked;
public HackyViewPager(Context context) {
super(context);
isLocked = false;
}
public HackyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
isLocked = false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (!isLocked) {
try {
return super.onInterceptTouchEvent(ev);
} catch (IllegalArgumentException e) {
e.printStackTrace();
return false;
}
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return !isLocked && super.onTouchEvent(event);
}
public void toggleLock() {
isLocked = !isLocked;
}
public void setLocked(boolean isLocked) {
this.isLocked = isLocked;
}
public boolean isLocked() {
return isLocked;
}
}
CutomShapeImageView
特点:可让你的ImageView自定义形状,包括但不限于圆形,矩形,三角形,以及各种不规则图形,github地址:https://github.com/MostafaGazar/CustomShapeImageView
原理:
BaseImageView在onDraw方法中去首先绘制src图片,再绘制遮罩图片(maskBitmap),需要注意的是绘制遮罩图片时候使用的Xfermode为PorterDuff.Mode.DST_IN,即在2图交集区域取目标图片的内容;
遮罩图片通过getBitmap()方法获取,该方法由子类实现:CircleImageView的实现是返回一个圆形空白图片,RectangleImageView的实现是返回一个矩形空白图片,SvgImageView的实现是将svg格式的图片解析成bitmap;
SVG介绍:
全称为ScaleVectorGraphic,即可伸缩的矢量图;
特点:体积小,不会失真,通过xml描述,所以可以直接被文本编辑器来修改和查看; 属于W3C标准之一;
详情查看:http://blog.csdn.net/tianjian4592/article/details/44733123/
用法如下:
使用CircleImageView,只需给src指定底图就行:
<com.meg7.widget.CircleImageView
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginLeft="16dp"
android:src="@drawable/sample_2"
android:scaleType="centerCrop">
使用RectangleImageView,同样只需要指定src底图:
<com.meg7.widget.RectangleImageView
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginLeft="16dp"
android:src="@drawable/sample_3"
android:scaleType="centerCrop" />
使用SvgImageView,需要给它指定一张svg格式的图片资源:
<com.meg7.widget.SvgImageView
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginLeft="16dp"
android:src="@drawable/sample_4"
app:svg_raw_resource="@raw/shape_star"
android:scaleType="centerCrop" />
MPAndroidChart
一个非常强大的用于数据统计展示的View,支持多样的折线图,柱状图,饼状图,网状图,点状图等等,基本上可以满足我们所有的需求,github地址:https://github.com/PhilJay/MPAndroidChart
原理(该类库太庞大,只进行简单分析):
基类Chart继承ViewGroup,在onDraw基本没有做什么事;
有很多实现类,如LineChart(线状图),BarChart(柱状图),PieChart(饼状图)等,他们都重写了onDraw方法,并按照如下步骤绘制出内容:
//1.绘制背景
drawGridBackground(canvas);
//2.绘制X,y轴线
mXAxisRenderer.renderAxisLine(canvas);
mAxisRendererLeft.renderAxisLine(canvas);
mAxisRendererRight.renderAxisLine(canvas);
//3.绘制网格线
mXAxisRenderer.renderGridLines(canvas);
mAxisRendererLeft.renderGridLines(canvas);
mAxisRendererRight.renderGridLines(canvas);
//4.绘制图形数据,如绘制线,绘制柱形,绘制饼状
mRenderer.drawData(canvas);
//5.绘制单位标签
mXAxisRenderer.renderAxisLabels(canvas);
mAxisRendererLeft.renderAxisLabels(canvas);
mAxisRendererRight.renderAxisLabels(canvas)
//6.绘制图形的数值
mRenderer.drawValues(canvas);
//7.绘制mark标记
drawMarkers(canvas);
//8.绘制表格描述说明
drawDescription(canvas);
用法如下(只拿线状图,柱状图,饼状图举例):
如何使用线状图:
mChart = (LineChart) findViewById(R.id.line_chart);
//设置没有数据时候展示的文本
mChart.setNoDataText("目前木有数据哦");
//1.给Chart设置数据
mChart.setData(getLineData());
//2.设置y轴的取值范围
YAxis axisLeft = mChart.getAxisLeft();
//设置y轴最小值
axisLeft.setAxisMinValue(0);
//设置y轴最大值
axisLeft.setAxisMaxValue(50);
//3.设置去掉右边的y轴线
YAxis axisRight = mChart.getAxisRight();
axisRight.setEnabled(false);
//4.设置是否启用x轴线
XAxis xAxis = mChart.getXAxis();
xAxis.setEnabled(true);//显示x轴线
//5.设置表格描述信息
mChart.setDescription("表格描述");
//6.设置是否绘制表格背景
mChart.setDrawGridBackground(true);
//设置表格网格背景的颜色
//mChart.setGridBackgroundColor(Color.BLUE);
//7.设置绘制动画的时间
mChart.animateXY(3000,3000);
给线状图准备数据,需要构造LineDataSet,每一条线的数据就是一个LineDataSet对象, 该对象可以设置线的颜色,线条的标签,粗细等
public LineData getLineData() {
int maxX = 10;
List<Entry> entryList = new ArrayList<>();
List<Entry> entryList2 = new ArrayList<>();
for (int i=0;i<maxX; i++){
//每个entry相当于线条上面的一个点
Entry entry = new BarEntry(i*5,i);
entryList.add(entry);
Entry entry2 = new BarEntry(i*6+5,i);
entryList2.add(entry2);
}
//创建LineDataSet对象,表示一条线的数据
LineDataSet dataSet = new LineDataSet(entryList,"Line1");
LineDataSet dataSet2 = new LineDataSet(entryList2,"Line2");
//设置线条颜色和宽度
dataSet.setLineWidth(4);
dataSet.setColor(Color.RED);
//
//dataSet.setDrawCircleHole(false);//是否绘制空心圆
//是否绘制圆形
dataSet.setDrawCircles(false);//不绘制线条上面的圆
//创建集合,存储所有线条数据对象
List<ILineDataSet> list = new ArrayList<>();
//存放一条线的数据
list.add(dataSet);
list.add(dataSet2);
//生成x轴的数据
List<String> xVals = ChartData.generateXVals(0, maxX);
LineData lineData = new LineData(xVals,list);
return lineData;
}
如何使用柱状图:
//创建条形数据对象
BarChart barChart = new BarChart(this);
setContentView(barChart);
//设置条形数据
barChart.setData(getBarData());
//设置描述
barChart.setDescription("2016行业薪资比较");
//设置绘制bar的阴影
barChart.setDrawBarShadow(true);
//设置绘制的动画时间
barChart.animateXY(3000,3000);
如何给柱状图设置数据,主要是构造BarDataSet对象,它需要一个装有BarEntry的集合, 每一个柱形都是一个BarEntry:
public BarData getBarData() {
int maxX = 10;
//创建集合,存放每个柱子的数据
List<BarEntry> list = new ArrayList<>();
List<BarEntry> list2 = new ArrayList<>();
for (int i=0;i<maxX;i++){
//一个BarEntry就是一个柱子的数据对象
BarEntry barEntry = new BarEntry(i+5,i);
list.add(barEntry);
BarEntry barEntry2 = new BarEntry(i+3,i);
list2.add(barEntry2);
}
//创建BarDateSet对象,其实就是一组柱形数据
BarDataSet barSet = new BarDataSet(list,"Android");
BarDataSet barSet2 = new BarDataSet(list2,"iOS");
//设置柱形的颜色
barSet.setColor(Color.BLUE);
//设置是否显示柱子上面的数值
barSet.setDrawValues(false);
//设置柱子阴影颜色
barSet.setBarShadowColor(Color.GRAY);
//创建集合,存放所有组的柱形数据
List<IBarDataSet> dataSets = new ArrayList<>();
dataSets.add(barSet);
dataSets.add(barSet2);
BarData barData = new BarData(ChartData.generateXVals(0,maxX),dataSets);
return barData;
}
如何使用饼状图:
PieChart pieChart = new PieChart(this);
setContentView(pieChart);
//设置饼状图数据
pieChart.setData(getPieData());
//设置描述
pieChart.setDescription("胡萝卜维生素成分比例");
pieChart.setDescriptionTextSize(20);
//设置中心说明文字
pieChart.setCenterText("中心说明文字");
pieChart.setCenterTextSize(20);
pieChart.setCenterTextColor(Color.RED);
pieChart.animateXY(3000,3000);
如何给饼状图设置数据,主要构造PieData对象,它需要一个PieDataSet对象,set对象是装满Entry的集合,封装的是饼状百分比和位置的映射:
public PieData getPieData() {
ArrayList<Entry> list = new ArrayList<>();
for (int i=0;i<3;i++){
//一个Entry就是一个饼块
Entry entry = new Entry(i+2,i);
list.add(entry);
}
//创建一组饼块的数据
PieDataSet pieDataSet = new PieDataSet(list,"标签");
//设置饼块的间距
pieDataSet.setSliceSpace(4);
//设置饼快颜色
pieDataSet.setColors(new int[]{Color.RED,Color.GREEN,Color.BLUE});
//创建x轴
ArrayList<String> xList = new ArrayList<>();
xList.add("维生素A");
xList.add("维生素B");
xList.add("维生素C");
PieData pieData = new PieData(xList,pieDataSet);
return pieData;
}