2015-03-26 09:17:52 yubo_725 阅读数 734
  • 通往Android的神奇之旅

    本套Android课程,大家可以一路学习基本控件,控件,网络,自定义View,地图,推送,后端云,项目实战2048游戏等等,通过本套课程,相信大家可以基本掌握Android入门知识。

    82273 人正在学习 去看看 刘桂林

下面用一个例子记录如何在Android中分页加载ListView的数据:

程序运行的结果如下:


点击加载更多后,会加载新的数据并显示在ListView中,当数据达条数达到某个上限后,点击加载更多不再显示

一、定义footerView 

“点击加载更多”是一个单独的布局,layout文件代码如下(footer_view.xml):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="45dp"
        android:gravity="center"
        android:orientation="horizontal" >

        <ProgressBar
            android:id="@+id/footer_progressbar"
            style="@android:style/Widget.ProgressBar.Small.Inverse"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="5dp"
            android:visibility="gone" />

        <TextView
            android:id="@+id/footer_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="点击加载更多"
            android:textColor="#000000"
            android:textSize="15sp" />
    </LinearLayout>

</LinearLayout>
该布局里就是一个ProgressBar和一个TextView,其中ProgressBar默认是隐藏的,加载数据时才显示

二、MainActivity的布局文件(activity_main.xml):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    
    <ListView
        android:id="@+id/listview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        />
    

</LinearLayout>
很简单,里面就放了一个ListView用于显示数据

三、ListView的Item布局(list_item.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    
    <TextView 
        android:id="@+id/list_item_tv"
        android:layout_width="fill_parent"
        android:layout_height="45dp"
        android:paddingLeft="10dp"
        android:gravity="center_vertical"
        android:textColor="#000000"
        android:textSize="15sp"
        />

</LinearLayout>
也很简单,就一个TextView

四、定义ListView的Adapter(ListAdapter.java)

package com.example.testlistview;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

public class ListAdapter extends BaseAdapter {
	private Context context;
	private List<String> data;
	
	public ListAdapter(Context context){
		this.context = context;
		data = new ArrayList<String>();
	}
	
	public void addItem(String item){
		this.data.add(item);
	}

	@Override
	public int getCount() {
		return data.size();
	}

	@Override
	public String getItem(int position) {
		return data.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		if(convertView == null){
			convertView = LayoutInflater.from(context).inflate(R.layout.list_item, null);
		}
		TextView tv = (TextView) convertView.findViewById(R.id.list_item_tv);
		tv.setText(getItem(position));
		return convertView;
	}

}

该ListAdapter中加入了一个addItem()方法,在ListView中新增数据时会用到该方法

五、MainActivity.java代码

package com.example.testlistview;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {
	private ListView listView;
	private ListAdapter adapter;
	private View footerView;
	private ProgressBar footerPb;
	private TextView footerTv;
	private int pageIndex = 1;//当前加载到哪一页
	private int pageSize = 10;//每一页的数据条数

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		initView();
		initData();
	}
	
	private void initView(){
		listView = (ListView) findViewById(R.id.listview);
		adapter = new ListAdapter(this);
		footerView = LayoutInflater.from(this).inflate(R.layout.footer_view, null);
		footerPb = (ProgressBar) footerView.findViewById(R.id.footer_progressbar);
		footerTv = (TextView) footerView.findViewById(R.id.footer_tv);
		listView.addFooterView(footerView);
	}
	
	private void initData(){
		for(int i = 0; i < 15; i++){
			adapter.addItem("item " + i);
		}
		listView.setAdapter(adapter);
		listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

			@Override
			public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
					long arg3) {
				if(arg2 == adapter.getCount()){
					//点击的是footerView,加载更多
					new LoadMoreDataTask().execute();
				}else{
					//点击的是ListView的数据项
					Toast.makeText(MainActivity.this, adapter.getItem(arg2), Toast.LENGTH_SHORT).show();
				}
			}
		});
	}
	
	private class LoadMoreDataTask extends AsyncTask<Void, Void, Void>{
		
		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			//修改footerView的显示状态
			footerPb.setVisibility(View.VISIBLE);
			footerTv.setText("正在加载...");
		}

		@Override
		protected Void doInBackground(Void... arg0) {
			try {
				//模拟网络请求获取数据
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			return null;
		}
		
		@Override
		protected void onPostExecute(Void result) {
			super.onPostExecute(result);
			//修改footerView的显示状态
			footerPb.setVisibility(View.GONE);
			footerTv.setText("点击加载更多");
			pageIndex++;
			int count = adapter.getCount();
			//模拟增加数据
			for(int i = count; i < count + pageSize; i++){
				adapter.addItem("new item " + i);
			}
			adapter.notifyDataSetChanged();
			//模拟数据取完,将ListView的FooterView移除
			if(pageIndex * pageSize >= 35){
				listView.removeFooterView(footerView);
			}
		}
		
	}

}
上面的代码用一个AsyncTask模拟了网络请求获取数据,在开始请求前改变footerView的显示状态,结束请求后又修改footerView的状态,并且在结束请求后判断数据是否加载完成,如果加载完成,就移除ListView的footerView






2016-08-21 17:45:50 u012949047 阅读数 817
  • 通往Android的神奇之旅

    本套Android课程,大家可以一路学习基本控件,控件,网络,自定义View,地图,推送,后端云,项目实战2048游戏等等,通过本套课程,相信大家可以基本掌握Android入门知识。

    82273 人正在学习 去看看 刘桂林

最近在项目中用到一问题: 检查了很多遍代码逻辑和代码并没有问题,但就是报空指针.终于找到了问题的原因:  view渲染



以上是我默认选中第一个页面 运行之后 报空指针 

修改之后: 干掉上面的代码


运行之后问就解决啦.

问题总结:

loadingPager是我设置的一个控制器.

加载数据,都是围绕MVC的模式来控制,这里创建一个控制器 .哪里需要视图就通过这样一个控制器将视图  返回回去.

渲染一个对象,和渲染一个View图所用的时间不是一个量级,当Fragment对象对象创建完毕的时候,View可以能还没有渲染完成,但是所有的View都是在loadingPager中渲染的,所以这个时候loadingFragment这个时候可能会为空,这个时候触发就会导致后面出现空指针异常.

解决办法: 设置View的监听.


2017-08-14 19:42:18 pzm1993 阅读数 5740
  • 通往Android的神奇之旅

    本套Android课程,大家可以一路学习基本控件,控件,网络,自定义View,地图,推送,后端云,项目实战2048游戏等等,通过本套课程,相信大家可以基本掌握Android入门知识。

    82273 人正在学习 去看看 刘桂林

动画的种类

Android动画可以分为以下3种:
View Animation
视图动画,通多对整个视图不断做图像的变换(平移、缩放、旋转、透明度)产生的动画效果,是一种渐进式动画。

Drawable Animation
图片动画,其实也是逐帧动画,是通过一个接一个的加载Drawable资源来创建动画,按顺序的播放,像一个胶卷。对于视图动画,他只是单独的图片内容在变换,而不是整个视图。很显然,如果图片过多多大就会导致OOM异常。

Property Animation
属性动画,在Android 3.0的(API级别11)引入的,该属性动画系统可以制作动画的任何对象的属性。但是一般来说,属性动画系统是首选的使用方法,因为它更灵活,并提供更多功能。

对于动画的分类,也有不同的,分为2类:View Animation 和 Property Animation,其中 View Animation又可以分为:补间动画和逐帧动画。其实内容多是以上三种,就不纠结这个了。
接下来,将会介绍这三种动画,并实现demo。这篇的主角是View Animation。

View Animation

视图动画的作用对象只能是View,在同一个图形通过在界面上进行透明度,缩放,旋转,平移的变化。

View动画的分类:

view动画支持4中动画效果,分别是:
  • 透明度动画(AlphaAnimation)
  • 缩放动画(ScaleAnimation)
  • 平移动画(TranslateAnimation)
  • 旋转动画(RotateAnimation)
如图所示,图中几个类多是Animation的子类,在java代码中具体实现也是通过这些子类来实现



这4种动画既能分开独立实现,也可以组合实现复合动画AnimationSet。如果这些动画还是满足不了你,可以自定义动画。不过,后面有了属性动画之后,大部分还是可以满足需求。

view动画的实现可以通过Xml来定义,也可以通过java代码来动态设置。对于view动画,建议使用XML来定义动画,可读性好,而且能过复用。而对于属性动画,则建议是通过代码动态设置,后文会介绍。

对于XML实现,有如下诸多属性,当然也不止这些
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@[package:]anim/interpolator_resource"
    android:shareInterpolator=["true" | "false"] >
    <alpha
        android:fromAlpha="float"
        android:toAlpha="float" />
    <scale
        android:fromXScale="float"
        android:toXScale="float"
        android:fromYScale="float"
        android:toYScale="float"
        android:pivotX="float"
        android:pivotY="float" />
    <translate
        android:fromXDelta="float"
        android:toXDelta="float"
        android:fromYDelta="float"
        android:toYDelta="float" />
    <rotate
        android:fromDegrees="float"
        android:toDegrees="float"
        android:pivotX="float"
        android:pivotY="float" />
    <set>
        ...
    </set>
</set>

首先先介绍他们公用的属性,其余在单独实现的实现的实现的再介绍:

XML属 性:android:duration             
关联方法:setDuration(long)           
说明:动画持续时间, 默认值是0 (单位ms)                          

XML属 性:android:fillAfter             
关联方法:setFillAfter(boolean)           
说明:表示动画结束后是否保留动画后的状态,true保留动画后状态,false恢复原来状态,默认值是false                          

XML属 性:android:fillBefore           
关联方法:setFillBefore(boolean)       
说明:表示动画结束后是否保留动画前的状态,true恢复原来状态,false保留动画后状态,默认值是true

XML属 性:android:fillEnabled           
关联方法:setFillEnabled(boolean)       
说明:如果设置为true,将fillBefore设置考虑在内                      

XML属 性:android:interpolator         
关联方法:setInterpolator(Interpolator) 
说明:设置动画的变化速率 即插值器,改变动画变换的速度,默认值是@android:anim/accelerate_decelerate_interpolator,即加速减速插值器,在动画开始和结束的时速度较慢,中间时候加速                      

XML属 性:android:repeatCount       
关联方法:setRepeatCount(int)         
说明:设置动画重复执行的次数  ,默认值是0                          
 
XML属 性:android:repeatMode       
关联方法:setRepeatMode(int)           
说明:设置动画重复的模式,其值可以有,restart( 1 ),表示顺序播放,reverse(2)表示重复的时候逆向播放                         
 
XML属 性:android:startOffset       
关联方法:setStartOffset(long)         
说明:设置开始的延迟的时间(单位ms)  ,默认值是0                 
 
坐标类型:
Animation.ABSOLUTE : 绝对数值(默认以px为单位)
Animation.RELATIVE_TO_SELF : 百分数,如:50% (以当前视图的宽度或高度为基数来计算)   
Animation.RELATIVE_TO_PARENT : 百分数+p,如:50%p (以父视图的宽度或高度为基数来计算)

设置监听:view.setAnimationListener(AnimationListener)
启动动画 : view.startAnimation(animation);
结束动画: view.clearAnimation();
.....

下面将详细对每个动画都通过这个2中方式分别实现,XMl实现可读性好,复用性强;java代码实现灵活、快速,因此这2中方式适用于不同的场景时,多比较常用。

准备工作:
1.使用AndroidStudio(或者其他)新建一个工程项目;
2.在res目录下新建anim资源文件(属性动画是新建animator文件);

AlphaAnimation

透明度动画,改变视图整体透明度,透明度值由1~0,从可见到不可见的变化,它的属性较少,最简单的一个。

在XML实现:
首先,新建一个alpha_demo.xml文件,在anim文件,点击右键,new->Android resource file, 文件名:alpha_demo,根节点:alpha
ok,该动画是通过标签<alpha/>实现的,实现代码如下:
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromAlpha="0.0"
    android:toAlpha="1.0"
    android:duration="2000">
</alpha>
它属性含义如下:
android:fromAlpha: 表示透明度的起始值,这里设置为0.0,表示完全透明,取值范围0~1;
android:toAlpha:表示透明度的结束值,这里设置为1.0,表示完全不透明,取值范围0~1;;
android:duration 表示动画持续的时间,这里设置为2000,单位是毫秒;

那现在如何java代码中使用这个xml呢? 使用AnimationUtils类的静态方法loadAnimation()来加载XML文件,得到一个Animation对象,如下:
Animation animation = AnimationUtils.loadAnimation(this, R.anim.alpha);
mImage.startAnimation(animation);
所有的属性多在xml定义好了,这里直接开始动画即可,就是这么简单。

在java实现:
要实现上面一样的功能,就需要用到Animation的子类AlphaAnimation来实现,代码如下:
        AlphaAnimation alphaAnimation = new AlphaAnimation(0f, 1f);
        alphaAnimation.setDuration(3000);
        mImage.startAnimation(alphaAnimation);
构造方法:
1.public AlphaAnimation (Context context, AttributeSet attrs)
context:当前上下文
attrs:xml中读取的属性设置

2.public AlphaAnimation (float fromAlpha, float toAlpha)
fromAlpha:动画开始的透明度
toAlpha:动画结束的透明度
ps:第一个构造函数较少用,一般在自定义动画的时候会用到(下面的动画也是一样);第2个方法很直观,也是最常用的。

ScaleAnimation

缩放动画,需要一个坐标点来,即轴点,实现以不同的轴点的缩放效果不同,因此需要先指定pivotX,pivotY确定轴点坐标。默认情况下,从对象view的左上角开始缩放,注意,不是父控件。

在xml实现:
该动画是通过标签<scale/>实现的,实现代码如下:
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
       android:duration="3000"
       android:fromXScale="0.0"
       android:fromYScale="0.0"
       android:pivotX="50%"
       android:pivotY="50%"
       android:toXScale="1.0"
       android:toYScale="1.0">
</scale>
属性含义如下:
android:fromXScale:动画开始时,水平方向缩放系数 float型值
android:fromYScale:动画开始时,垂直方向缩放系数float型值
android:toXScale:动画结束时,水平方向缩放系数float型值
android:toYScale:动画结束时,垂直方向缩放系数float型值
android:pivotX:缩放轴点的X坐标(其值可以为:数值、百分数、百分数p),例如:如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点)
android:pivotY:缩放轴点的Y坐标,规律同pivotX。

在java中加载并开始该动画:
 	Animation animation = AnimationUtils.loadAnimation(this, R.anim.scale);
        mImage.startAnimation(animation);

在java中实现:
需要使用Animation的子类ScaleAnimation来实现,代码如下:
        ScaleAnimation scaleAnimation = new ScaleAnimation(0, 1, 0, 1, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        scaleAnimation.setDuration(3000);
        mImage.startAnimation(scaleAnimation);
构造函数:
1.public ScaleAnimation(Context context, AttributeSet attrs)(同上)

2.public ScaleAnimation(float fromX, float toX, float fromY, float toY)
fromX:动画开始时,水平方向缩放系数float型值
toX:动画开始时,垂直方向缩放系数float型值
fromY:动画结束时,水平方向缩放系数float型值
toY:动画结束时,垂直方向缩放系数float型值

3.public ScaleAnimation(float fromX, float toX, float fromY, float toY, float pivotX, float pivotY)
pivotX:表示轴点在x方向坐标值,相对于该对象绝对像素位置,0表示该对象的左边缘坐标
pivotY:表示轴点在y方向坐标值,相对于该对象绝对像素位置,0表示该对象的上边缘坐标
ps:该构造函数 ,坐标类型默认是:Animation.ABSOLUTE ,因此,作用与xml中android:pivotX为数值时 对应

4.public ScaleAnimation(float fromX, float toX, float fromY, float toY, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)
pivotXType:X轴坐标的类型(不同类型,计算x轴上的偏移量的方式不同)
pivotXValue:表示轴点在x方向坐标值
pivotYType:Y轴坐标的类型(不同类型,计算y轴上的偏移量的方式不同)
pivotYValue:表示轴点在y方向坐标值

ps:
当pivotXType=Animation.ABSOLUTE时:pivotXValue的值代表绝对像素,与xml中android:pivotX为数值时对应
当pivotXType=Animation.RELATIVE_TO_SELF时:pivotXValue的值代表相对于当前View定位,与xml中android:pivotX为百分数时对应
当pivotXType=Animation.RELATIVE_TO_PARENT:pivotXValue的值代表相对于当前View的父控件定位,与xml中android:pivotX为百分数p时对应

RotateAnimation

旋转动画,与缩放动画较为相似,也需要一个轴点来实现旋转。默认情况下,从对象view的左上角开始旋转,也即是相对于当前view坐标为(0,0)位置。

在xml实现:
该动画是通过标签</rotate>实现的,实现代码如下:
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="3000"
        android:pivotX="50%"
        android:pivotY="50%"
        android:fromDegrees="0"
        android:toDegrees="90">

</rotate>
属性含义:
android:pivotX:旋转轴点的X坐标(其值可以为:数值、百分数、百分数p),例如:如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点)
android:pivotY:旋转轴点的Y坐标,规律同android:pivotX。
android:fromDegrees:旋转开始的角度,其值可以为正负
android:toDegrees:旋转结束的角度,其值可以为正负
ps:toDegrees -fromDegrees > 0,则顺时针旋转;否则,逆时针旋转。

在java中加载并开始该动画:
	Animation animation = AnimationUtils.loadAnimation(mContext, R.anim.rotate);
        mImage.startAnimation(animation);

在java中实现:
需要使用Animation的子类RotateAnimation 来实现,代码如下:
	RotateAnimation rotateAnimation = new RotateAnimation(0,180);
        rotateAnimation.setDuration(3000);
        mImage.startAnimation(rotateAnimation);
构造函数:
1.public RotateAnimation(Context context, AttributeSet attrs)(同上)

2.public RotateAnimation(float fromDegrees, float toDegrees)
fromDegrees:旋转开始的角度,其值可以为正负
toDegrees:旋转结束的角度,其值可以为正负

3.public RotateAnimation(float fromDegrees, float toDegrees, float pivotX, float pivotY)
pivotX:表示轴点在x方向坐标值,相对于该对象绝对像素位置,0表示该对象的左边缘坐标
pivotY:表示轴点在y方向坐标值,相对于该对象绝对像素位置,0表示该对象的上边缘坐标
ps:该构造函数 ,坐标类型默认是:Animation.ABSOLUTE ,因此,作用与xml中android:pivotX为数值时 对应

4.public RotateAnimation(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue, int pivotYType)
pivotXType:X轴坐标的类型(不同类型,计算x轴上的偏移量的方式不同)
pivotXValue:表示轴点在x方向坐标值
pivotYType:Y轴坐标的类型(不同类型,计算y轴上的偏移量的方式不同)
pivotYValue:表示轴点在y方向坐标值

TranslateAnimation

平移动画,实现视图垂直/水平方向位移变化,指定开始的位置,和结束的位置即可。

在xml实现:
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
           android:fillAfter="true"
           android:duration="3000"
           android:fromXDelta="50%"
           android:fromYDelta="50%"
           android:toXDelta="50%p"
           android:toYDelta="50%p">
</translate>
属性含义如下:
android:fromXDelta:平移开始X方向坐标(其值可以为:数值、百分数、百分数p,且多可以为正负),其值含义与android:pivotX类似
android:fromYDelta:平移开始Y方向坐标(其值可以为:数值、百分数、百分数p,且多可以为正负),其值含义与android:pivotY类似
android:toXDelta:平移结束X方向坐标(其值可以为:数值、百分数、百分数p,且多可以为正负),其值含义与android:pivotX类似
android:toYDelta:平移结束Y方向坐标(其值可以为:数值、百分数、百分数p,且多可以为正负),其值含义与android:pivotY类似

在java中加载并开始该动画:
	Animation animation = AnimationUtils.loadAnimation(this, R.anim.translate);
        mImage.startAnimation(animation);

在java中实现:
需要使用Animation的子类TranslateAnimation来实现,代码如下:
	TranslateAnimation translateAnimation = new TranslateAnimation(Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_PARENT,0,Animation.RELATIVE_TO_PARENT,0.5f);
        translateAnimation.setDuration(3000);
        translateAnimation.setFillAfter(true);
        mImage.startAnimation(translateAnimation);
构造函数:
1. public TranslateAnimation(Context context, AttributeSet attrs)(同上)

2.public TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta)
fromXDelta:表示在平移开始X方向的绝对像素值
toXDelta:表示在平移开始Y方向的绝对像素值
fromYDelta:表示在平移结束X方向的绝对像素值
toYDelta:表示在平移结束Y方向的绝对像素值
ps:该构造函数 ,坐标类型默认是:Animation.ABSOLUTE ,因此,作用与xml中android:fromXDelta为数值时 对应

3.public TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue, int fromYType, float fromYValue, int toYType, float toYValue)
fromXType:平移开始X轴的坐标类型(不同类型,计算x轴上的偏移量的方式不同)
fromXValue:平移开始X轴的坐标,其值的含义与fromXType的类型有关
toXType:平移结束Y轴的坐标类型(不同类型,计算x轴上的偏移量的方式不同)
toXValue:平移结束X轴的坐标,其值的含义与toXType的类型有关
fromYType:平移开始Y轴的坐标类型(不同类型,计算x轴上的偏移量的方式不同)
fromYValue:平移开始Y轴的坐标,其值的含义与fromYType的类型有关
toYType:平移结束Y轴的坐标类型(不同类型,计算x轴上的偏移量的方式不同)
fromXType:平移结束Y轴的坐标,其值的含义与toYType的类型有关

AnimationSet

集合动画,如果单一的动画太过于单调,那么就可以将这些单一的动画组合成个性酷炫的动画,同时指定播放的顺序,并且集合里面可以再包含集合。但注意的是,在集合设置的属性对该标签下的所有子控件都产生影响。

在xml实现:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:shareInterpolator="false">
    <scale
        android:duration="2000"
        android:fromXScale="1.0"
        android:fromYScale="1.0"
        android:interpolator="@android:anim/linear_interpolator"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="0"
        android:toYScale="0"/>

    <set
        android:duration="2000"
        android:interpolator="@android:anim/decelerate_interpolator"
        android:shareInterpolator="true"
        android:startOffset="2000">
        <scale
            android:fromXScale="0"
            android:fromYScale="0"
            android:pivotX="50%"
            android:pivotY="50%"
            android:toXScale="1"
            android:toYScale="1"/>
        <rotate
            android:fromDegrees="0"
            android:pivotX="50%"
            android:pivotY="50%"
            android:toDegrees="180"/>
    </set>
</set>
属性含义:
android:shareInterpolator:子元素是否共享插值器,值为true,表示共同使用;值为false,表示不共享
其余属性多和其他类似,这里如果需要设置播放的顺序,则需要设置android:startOffset属性
在java中加载并开始该动画:
	Animation animation = AnimationUtils.loadAnimation(mContext,R.anim.set);
        mImage.startAnimation(animation);

在java中实现:
需要使用Animation的子类AnimationSet 来实现,代码如下:
	AnimationSet animationSet1 = new AnimationSet(false);//一级集合
        ScaleAnimation scaleAnimation1 = new ScaleAnimation(1, 1.4f, 1, 1.4f, Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);

        AnimationSet animationSet2 = new AnimationSet(true);//二级集合
        ScaleAnimation scaleAnimation2 = new ScaleAnimation(1.4f, 0, 1.4f, 0, Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);
        RotateAnimation rotateAnimation = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);

        animationSet2.addAnimation(scaleAnimation2);
        animationSet2.addAnimation(rotateAnimation);
        animationSet2.setInterpolator(new DecelerateInterpolator());
        animationSet2.setDuration(2000);
        animationSet2.setStartOffset(2000);

        animationSet1.addAnimation(scaleAnimation1);
        animationSet1.addAnimation(animationSet2);
        animationSet1.setInterpolator(new AccelerateDecelerateInterpolator());
        animationSet1.setDuration(2000);

        mImage.startAnimation(animationSet1);
构造函数:
1.AnimationSet(Context context, AttributeSet attrs)(同上)
2.public AnimationSet(boolean shareInterpolator)
shareInterpolator:是否共享插值器

动画的监听

如果我们在动画变化的过程中,需要添加什么功能,那么就可以设置监听,比如:
 translateAnimation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
                
            }

            @Override
            public void onAnimationEnd(Animation animation) {

            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });
可以监听动画开始、结束、重复三种状态。

注意:无论您的动画如何移动或调整大小,保存动画的视图的边界将不会自动调整以适应。即使如此,动画仍将被绘制超出其视图的边界,不会被剪辑。但是,如果动画超过父视图的边界,则会发生裁剪。

问题:你是否真正的明白 pivotX 的含义?
轴点坐标值pivotX,pivotY,有三种表达方式:在xml文件,其值有3种类型:数值、百分数、百分数p;在java代码中,其值要根据,三种坐标类型(前方有介绍)来确定。
一般,对于表达方式的第一种、第二种,应该是比较常用的,而且也是比较好理解的,也比较直观。那么问题来了:

提出问题:
如果是使用第三种,相对于父控件定位,你是否能够准确找到轴点位置?是否知道其真正的含义?

猜想:
pivotX = 50%,那么轴点就在该控件(没有覆盖整个屏幕)的中间位置;pivotX = 50%p,那么中心点相对于父控件(覆盖了整个屏幕)就是屏幕的中间点。
对于这个想法,我...我...我...刚开始是这样子想的......

实践:
我们用平移动画来实践,保留动画结束的帧,将其参数设置为:
TranslateAnimation translateAnimation = new TranslateAnimation(Animation.RELATIVE_TO_PARENT, 0, Animation.RELATIVE_TO_PARENT, 0.5f, 
		Animation.RELATIVE_TO_PARENT ,0, Animation.RELATIVE_TO_PARENT, 0.5f);
从相当于父控件的(0,0)    移动到     父控件的(50%,50%),但实际效果是这样子的,与猜想不符:
         
                                                                                           

探索并解决问题:
我们可以点进去看TranslateAnimation ,可以发现:
@Override
    public void initialize(int width, int height, int parentWidth, int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);
        mFromXDelta = resolveSize(mFromXType, mFromXValue, width, parentWidth);
        mToXDelta = resolveSize(mToXType, mToXValue, width, parentWidth);
        mFromYDelta = resolveSize(mFromYType, mFromYValue, height, parentHeight);
        mToYDelta = resolveSize(mToYType, mToYValue, height, parentHeight);
    }
然后看resolveSize()方法:
protected float resolveSize(int type, float value, int size, int parentSize) {
        switch (type) {
            case ABSOLUTE:
                return value;
            case RELATIVE_TO_SELF:
                return size * value;
            case RELATIVE_TO_PARENT:
                return parentSize * value;
            default:
                return value;
        }
    }
也许,看到这里,反而觉得猜想没有错。。。莫急,其实动画真正的实现是在这里:
@Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        float dx = mFromXDelta;
        float dy = mFromYDelta;
        if (mFromXDelta != mToXDelta) {
            dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
        }
        if (mFromYDelta != mToYDelta) {
            dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
        }
        t.getMatrix().setTranslate(dx, dy);
    }
其中,第一个参数interpolatedTime为动画的进度时间值,取值范围为[0.0f,1.0f],第二个参数Transformation记录着动画某一帧中变形的原始数据。该方法在动画的每一帧显示过程中都会被调用。
这里,我们发现,动画需要绘制的轨迹是dx = mToXDelta ,dy = mToYDelta ,这个值是相对于该控件 需要变化的距离,而不是最终的位置,那么最终猜想:
如果该控件刚好位于屏幕的左上角,则mToXValue就是动画结束的位置;
如果该控件不在屏幕的左上角,则最终动画后的坐标需要加上该控件这个坐标;
最后用表达式表示:
toXValue = fromXType + dx;
toYType = fromYValue + dy;

验证:
因为VieAnimation动画,不能改变视图的属性,即使保留结束后的帧,动画结束后获取的坐标还是和动画前一样的。。。
那我们先得到屏幕的大小,通过简单的一些计算,得到动画结束的位置(通过上面的表达式),在这个位置放上一张图片,看是否可以这张图片重合,看图:
                                                                                                       
可以看到,最后的结论是正确的。
那么,现在你是否明白了pivotX的含义?
但是,然并卵,貌似也很少用到相对于父控件这个参数,笑哭!!!所以理解理解就好吧,哈哈哈。。。

其他使用场景

1.应用于ViewGroup的子元素
我们常用的ViewGroup,比如ListView,我们通常看到,ListView的每个子元素出现时多有一个特定的动画,那么是怎么实现的呢?学完上面的,会觉得特别的简单。
它使用的是LayoutAnimation,它也是一个View动画。下面给ListView添加动画,有以下步骤:

a.定义LayoutAnimation的xml文件,menu_setting_title_list.xml,如下:
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
    android:delay="0.2"
    android:animationOrder="normal"
    android:animation="@anim/left_in">
</layoutAnimation>
属性含义:
android:delay:子元素开始动画的延迟时间。比如,在@anim/left_in定义的动画周期为500ms,那么0.2表示每个item元素需要延迟100ms才出场播放,也可以为百分数。
android:animationOrder:表示子元素动画的顺序。有三种顺序:normal(从小到大正常顺序)、reverse(倒序)、random(随机)。
android:animation:子元素具体的入场动画。

b.给子元素定义具体的动画实现,即实现left_in.xml文件,如下:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="500"
        android:fromXDelta="-100%"
        android:toXDelta="0" />
</set>
很简单,从左滑到右边,这个效果即从隐藏到出现,时间为500ms。

c.给ListView指定LayoutAnimation属性,实现动画。
在xml指定:
<ListView
            android:id="@+id/listView_setting_title"
            android:layout_width="378dp"
            android:paddingTop="89.2dp"
            android:layout_height="match_parent"
            android:divider="@color/transparent"
            android:dividerHeight="4dp"
            android:layoutAnimation="@anim/menu_setting_title_list"
            android:listSelector="@color/transparent">
 </ListView>

在java代码指定:
//获取Animation对象
		Animation animation = AnimationUtils.loadAnimation(this, R.anim.menu_installation_list);
		//得到LayoutAnimationController对象
		LayoutAnimationController controller = new LayoutAnimationController(animation);
		//设置动画播放顺序
		controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
		//设置延迟时间
		controller.setDelay(0.2f);
		//listView设置LayoutAnimation,实现动画
		mMenuTitlesList.setLayoutAnimation(controller);
以上三步就实现LayoutAnimation的设置。

2.应用于Activity的切换
Activity切换过程中,系统有默认的动画,但是很普通。如果我们想自己定义,可以使用 overridePendingTransition(int enterAnim, int exitAnim)来指定动画。其中,
enterAnim:Activity被打开时的动画资源id
exitAnim:Activity被关闭时的动画资源id
代码如下:
startActivity(new Intent(this, MainActivity.class));
overridePendingTransition(R.anim.left_in, R.anim.right_out);
finish();
其中,left_in.xml,right_out.xml 就是自定义的动画

注意的是:该方法必须在startActivity()或者在finish()方法之后调到才有效果!!!切记

3.应用于Fragment的切换
Fragmnet同样也可以添加动画切换,这个应该相对比较少用到。它是通过FragmentTransaction的setCustomAnimations(int enter, int exit)方法来指定动画的。
enter:Fragmnet被打开时的动画资源id;
exit:Fragmne被关闭时的动画资源id;
Fragmnet是在api11才引入,这个动画需要view动画,如果导入的Fragment的是v4包,那么只能支持ViewAnimation,如果是android.app下的,那么也支持Property Animation。使用如下:
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction .setCustomAnimations(R.animator.cube_left_in, R.animator.cube_right_out); //使用属性动画
transaction.hide(mFrontFragment).add(R.id.fragment_layout, nextFragment, fragmentTag);
transaction.commit();

接下来看一个集合这3中场景的动画:(由于项目管理的东西比较大,这个就不贴代码了)
                                                                                          


好了,对于ViewAnimation的介绍就到这里了,每次总结希望可以给自己更深的印象,也能帮助读者。

最后,源码  —> 点我点我 
2014-02-16 23:38:15 xiangzhihong8 阅读数 1769
  • 通往Android的神奇之旅

    本套Android课程,大家可以一路学习基本控件,控件,网络,自定义View,地图,推送,后端云,项目实战2048游戏等等,通过本套课程,相信大家可以基本掌握Android入门知识。

    82273 人正在学习 去看看 刘桂林
最近项目中用到了ListView的下拉刷新的功能,总结了一下前辈们的代码,单独抽取出来写了一个demo作为示例。


效果图

下拉刷新:


加载更多:



CustomListView.java

  1. package com.example.uitest.view;  
  2.   
  3. import java.util.Date;  
  4. import com.example.uitest.R;  
  5. import android.content.Context;  
  6. import android.util.AttributeSet;  
  7. import android.util.Log;  
  8. import android.view.LayoutInflater;  
  9. import android.view.MotionEvent;  
  10. import android.view.View;  
  11. import android.view.ViewGroup;  
  12. import android.view.animation.LinearInterpolator;  
  13. import android.view.animation.RotateAnimation;  
  14. import android.widget.AbsListView;  
  15. import android.widget.AbsListView.OnScrollListener;  
  16. import android.widget.BaseAdapter;  
  17. import android.widget.ImageView;  
  18. import android.widget.LinearLayout;  
  19. import android.widget.ListView;  
  20. import android.widget.ProgressBar;  
  21. import android.widget.TextView;  
  22.   
  23. /** 
  24.  * ListView下拉刷新 
  25.  * 
  26.  */  
  27. public class CustomListView extends ListView implements OnScrollListener {  
  28.   
  29.     private final static int RELEASE_To_REFRESH = 0;  
  30.     private final static int PULL_To_REFRESH = 1;  
  31.     private final static int REFRESHING = 2;  
  32.     private final static int DONE = 3;  
  33.     private final static int LOADING = 4;  
  34.   
  35.     // 实际的padding的距离与界面上偏移距离的比例  
  36.     private final static int RATIO = 3;  
  37.   
  38.     private LayoutInflater inflater;  
  39.   
  40.     private LinearLayout headView;  
  41.   
  42.     private TextView tipsTextview;  
  43.     private TextView lastUpdatedTextView;  
  44.     private ImageView arrowImageView;  
  45.     private ProgressBar progressBar;  
  46.   
  47.   
  48.     private RotateAnimation animation;  
  49.     private RotateAnimation reverseAnimation;  
  50.   
  51.     // 用于保证startY的值在一个完整的touch事件中只被记录一次  
  52.     private boolean isRecored;  
  53.   
  54.     private int headContentWidth;  
  55.     private int headContentHeight;  
  56.   
  57.     private int startY;  
  58.     private int firstItemIndex;  
  59.   
  60.     private int state;  
  61.   
  62.     private boolean isBack;  
  63.   
  64.     private OnRefreshListener refreshListener;  
  65.     private OnLoadListener loadListener;  
  66.   
  67.     private boolean isRefreshable;  
  68.       
  69.     private ProgressBar moreProgressBar;  
  70.     private TextView loadMoreView;  
  71.     private View moreView;  
  72.   
  73.     public CustomListView(Context context) {  
  74.         super(context);  
  75.         init(context);  
  76.     }  
  77.   
  78.     public CustomListView(Context context, AttributeSet attrs) {  
  79.         super(context, attrs);  
  80.         init(context);  
  81.     }  
  82.   
  83.     private void init(Context context) {  
  84.         setCacheColorHint(context.getResources().getColor(R.color.transparent));  
  85.         inflater = LayoutInflater.from(context);  
  86.   
  87.         headView = (LinearLayout) inflater.inflate(R.layout.head, null);  
  88.   
  89.         arrowImageView = (ImageView) headView.findViewById(R.id.head_arrowImageView);  
  90.         arrowImageView.setMinimumWidth(70);  
  91.         arrowImageView.setMinimumHeight(50);  
  92.         progressBar = (ProgressBar) headView.findViewById(R.id.head_progressBar);  
  93.         tipsTextview = (TextView) headView.findViewById(R.id.head_tipsTextView);  
  94.         lastUpdatedTextView = (TextView) headView.findViewById(R.id.head_lastUpdatedTextView);  
  95.   
  96.         measureView(headView);  
  97.         headContentHeight = headView.getMeasuredHeight();  
  98.         headContentWidth = headView.getMeasuredWidth();  
  99.   
  100.         headView.setPadding(0, -1 * headContentHeight, 00);  
  101.         headView.invalidate();  
  102.   
  103.         Log.v("size""width:" + headContentWidth + " height:" + headContentHeight);  
  104.   
  105.         addHeaderView(headView, nullfalse);  
  106.         setOnScrollListener(this);  
  107.   
  108.         animation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f);  
  109.         animation.setInterpolator(new LinearInterpolator());  
  110.         animation.setDuration(250);  
  111.         animation.setFillAfter(true);  
  112.   
  113.         reverseAnimation = new RotateAnimation(-1800, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f);  
  114.         reverseAnimation.setInterpolator(new LinearInterpolator());  
  115.         reverseAnimation.setDuration(200);  
  116.         reverseAnimation.setFillAfter(true);  
  117.   
  118.         state = DONE;  
  119.         isRefreshable = false;  
  120.           
  121.         moreView = LayoutInflater.from(context).inflate(R.layout.listfooter_more, null);  
  122.         moreView.setVisibility(View.VISIBLE);  
  123.         moreProgressBar = (ProgressBar) moreView.findViewById(R.id.pull_to_refresh_progress);  
  124.         loadMoreView = (TextView) moreView.findViewById(R.id.load_more);  
  125.         moreView.setOnClickListener(new View.OnClickListener() {  
  126.               
  127.             @Override  
  128.             public void onClick(View v) {  
  129.                 onLoad();  
  130.             }  
  131.         });  
  132.         addFooterView(moreView);  
  133.     }  
  134.   
  135.     public void onScroll(AbsListView arg0, int firstVisiableItem, int arg2, int arg3) {  
  136.         firstItemIndex = firstVisiableItem;  
  137.     }  
  138.   
  139.     public void onScrollStateChanged(AbsListView arg0, int arg1) {  
  140.           
  141.     }  
  142.   
  143.     public boolean onTouchEvent(MotionEvent event) {  
  144.   
  145.         if (isRefreshable) {  
  146.             switch (event.getAction()) {  
  147.             case MotionEvent.ACTION_DOWN:  
  148.                 if (firstItemIndex == 0 && !isRecored) {  
  149.                     isRecored = true;  
  150.                     startY = (int) event.getY();  
  151.                 }  
  152.                 break;  
  153.   
  154.             case MotionEvent.ACTION_UP:  
  155.   
  156.                 if (state != REFRESHING && state != LOADING) {  
  157.                     if (state == DONE) {  
  158.                           
  159.                     }  
  160.                     if (state == PULL_To_REFRESH) {  
  161.                         state = DONE;  
  162.                         changeHeaderViewByState();  
  163.                     }  
  164.                     if (state == RELEASE_To_REFRESH) {  
  165.                         state = REFRESHING;  
  166.                         changeHeaderViewByState();  
  167.                         onRefresh();  
  168.                     }  
  169.                 }  
  170.   
  171.                 isRecored = false;  
  172.                 isBack = false;  
  173.   
  174.                 break;  
  175.   
  176.             case MotionEvent.ACTION_MOVE:  
  177.                 int tempY = (int) event.getY();  
  178.   
  179.                 if (!isRecored && firstItemIndex == 0) {  
  180.                     isRecored = true;  
  181.                     startY = tempY;  
  182.                 }  
  183.   
  184.                 if (state != REFRESHING && isRecored && state != LOADING) {  
  185.   
  186.                     // 保证在设置padding的过程中,当前的位置一直是在head,否则如果当列表超出屏幕的话,当在上推的时候,列表会同时进行滚动  
  187.                     // 可以松手去刷新了  
  188.                     if (state == RELEASE_To_REFRESH) {  
  189.   
  190.                         setSelection(0);  
  191.   
  192.                         // 往上推了,推到了屏幕足够掩盖head的程度,但是还没有推到全部掩盖的地步  
  193.                         if (((tempY - startY) / RATIO < headContentHeight) && (tempY - startY) > 0) {  
  194.                             state = PULL_To_REFRESH;  
  195.                             changeHeaderViewByState();  
  196.                         }  
  197.                         // 一下子推到顶了  
  198.                         else if (tempY - startY <= 0) {  
  199.                             state = DONE;  
  200.                             changeHeaderViewByState();  
  201.                         }  
  202.                         // 往下拉了,或者还没有上推到屏幕顶部掩盖head的地步  
  203.                     }  
  204.                     // 还没有到达显示松开刷新的时候,DONE或者是PULL_To_REFRESH状态  
  205.                     if (state == PULL_To_REFRESH) {  
  206.   
  207.                         setSelection(0);  
  208.   
  209.                         // 下拉到可以进入RELEASE_TO_REFRESH的状态  
  210.                         if ((tempY - startY) / RATIO >= headContentHeight) {  
  211.                             state = RELEASE_To_REFRESH;  
  212.                             isBack = true;  
  213.                             changeHeaderViewByState();  
  214.                         }  
  215.                         else if (tempY - startY <= 0) {  
  216.                             state = DONE;  
  217.                             changeHeaderViewByState();  
  218.                         }  
  219.                     }  
  220.   
  221.                     if (state == DONE) {  
  222.                         if (tempY - startY > 0) {  
  223.                             state = PULL_To_REFRESH;  
  224.                             changeHeaderViewByState();  
  225.                         }  
  226.                     }  
  227.   
  228.                     if (state == PULL_To_REFRESH) {  
  229.                         headView.setPadding(0, -1 * headContentHeight + (tempY - startY) / RATIO, 00);  
  230.   
  231.                     }  
  232.   
  233.                     if (state == RELEASE_To_REFRESH) {  
  234.                         headView.setPadding(0, (tempY - startY) / RATIO - headContentHeight, 00);  
  235.                     }  
  236.   
  237.                 }  
  238.   
  239.                 break;  
  240.             }  
  241.         }  
  242.   
  243.         return super.onTouchEvent(event);  
  244.     }  
  245.   
  246.     // 当状态改变时候,调用该方法,以更新界面  
  247.     private void changeHeaderViewByState() {  
  248.         switch (state) {  
  249.         case RELEASE_To_REFRESH:  
  250.             arrowImageView.setVisibility(View.VISIBLE);  
  251.             progressBar.setVisibility(View.GONE);  
  252.             tipsTextview.setVisibility(View.VISIBLE);  
  253.             lastUpdatedTextView.setVisibility(View.VISIBLE);  
  254.   
  255.             arrowImageView.clearAnimation();  
  256.             arrowImageView.startAnimation(animation);  
  257.             tipsTextview.setText("松开刷新");  
  258.   
  259.             break;  
  260.         case PULL_To_REFRESH:  
  261.             progressBar.setVisibility(View.GONE);  
  262.             tipsTextview.setVisibility(View.VISIBLE);  
  263.             lastUpdatedTextView.setVisibility(View.VISIBLE);  
  264.             arrowImageView.clearAnimation();  
  265.             arrowImageView.setVisibility(View.VISIBLE);  
  266.             // 是由RELEASE_To_REFRESH状态转变来的  
  267.             if (isBack) {  
  268.                 isBack = false;  
  269.                 arrowImageView.clearAnimation();  
  270.                 arrowImageView.startAnimation(reverseAnimation);  
  271.   
  272.                 tipsTextview.setText("下拉刷新");  
  273.             } else {  
  274.                 tipsTextview.setText("下拉刷新");  
  275.             }  
  276.             break;  
  277.   
  278.         case REFRESHING:  
  279.             headView.setPadding(0000);  
  280.             progressBar.setVisibility(View.VISIBLE);  
  281.             arrowImageView.clearAnimation();  
  282.             arrowImageView.setVisibility(View.GONE);  
  283.             tipsTextview.setText("正在刷新...");  
  284.             lastUpdatedTextView.setVisibility(View.VISIBLE);  
  285.   
  286.             break;  
  287.         case DONE:  
  288.             headView.setPadding(0, -1 * headContentHeight, 00);  
  289.   
  290.             progressBar.setVisibility(View.GONE);  
  291.             arrowImageView.clearAnimation();  
  292.             arrowImageView.setImageResource(R.drawable.arrow);  
  293.             tipsTextview.setText("下拉刷新");  
  294.             lastUpdatedTextView.setVisibility(View.VISIBLE);  
  295.   
  296.             break;  
  297.         }  
  298.     }  
  299.   
  300.     public void setonRefreshListener(OnRefreshListener refreshListener) {  
  301.         this.refreshListener = refreshListener;  
  302.         isRefreshable = true;  
  303.     }  
  304.       
  305.     public void setonLoadListener(OnLoadListener loadListener) {  
  306.         this.loadListener = loadListener;  
  307.     }  
  308.   
  309.     public interface OnRefreshListener {  
  310.         public void onRefresh();  
  311.     }  
  312.       
  313.     public interface OnLoadListener {  
  314.         public void onLoad();  
  315.     }  
  316.   
  317.     @SuppressWarnings("deprecation")  
  318.     public void onRefreshComplete() {  
  319.         state = DONE;  
  320.         lastUpdatedTextView.setText("最近更新:" + new Date().toLocaleString());  
  321.         changeHeaderViewByState();  
  322.     }  
  323.       
  324.     private void onLoad() {  
  325.         if (loadListener != null) {  
  326.             moreProgressBar.setVisibility(View.VISIBLE);  
  327.             loadMoreView.setText(getContext().getString(R.string.load_more));  
  328.             loadListener.onLoad();  
  329.         }  
  330.     }  
  331.       
  332.     public void onLoadComplete() {  
  333. //      moreView.setVisibility(View.GONE);  
  334.         moreProgressBar.setVisibility(View.GONE);  
  335.         loadMoreView.setText(getContext().getString(R.string.more_data));  
  336.     }  
  337.   
  338.     private void onRefresh() {  
  339.         if (refreshListener != null) {  
  340.             refreshListener.onRefresh();  
  341.         }  
  342.     }  
  343.   
  344.     private void measureView(View child) {  
  345.         ViewGroup.LayoutParams p = child.getLayoutParams();  
  346.         if (p == null) {  
  347.             p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);  
  348.         }  
  349.         int childWidthSpec = ViewGroup.getChildMeasureSpec(00 + 0, p.width);  
  350.         int lpHeight = p.height;  
  351.         int childHeightSpec;  
  352.         if (lpHeight > 0) {  
  353.             childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);  
  354.         } else {  
  355.             childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);  
  356.         }  
  357.         child.measure(childWidthSpec, childHeightSpec);  
  358.     }  
  359.   
  360.     @SuppressWarnings("deprecation")  
  361.     public void setAdapter(BaseAdapter adapter) {  
  362.         lastUpdatedTextView.setText("最近更新:" + new Date().toLocaleString());  
  363.         super.setAdapter(adapter);  
  364.     }  
  365.   
  366. }  


在 CustomListView 中有2个回调接口,OnRefreshListener 和 OnLoadListener ,分别对应 下拉和点击加载更多 时候的回调函数。在下拉刷新完成之后要调用 mListView.onRefreshComplete(); 来隐藏掉 头部,调用 mListView.onLoadComplete(); 隐藏掉 底部的加载view。


header.xml

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <!-- ListView的头部 -->  
  3.   
  4. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  5.     android:layout_width="fill_parent"  
  6.     android:layout_height="wrap_content" >  
  7.   
  8.     <!-- 内容 -->  
  9.   
  10.     <RelativeLayout  
  11.         android:id="@+id/head_contentLayout"  
  12.         android:layout_width="fill_parent"  
  13.         android:layout_height="wrap_content"  
  14.         android:paddingLeft="30dp" >  
  15.   
  16.         <!-- 箭头图像、进度条 -->  
  17.   
  18.         <FrameLayout  
  19.             android:layout_width="wrap_content"  
  20.             android:layout_height="wrap_content"  
  21.             android:layout_alignParentLeft="true"  
  22.             android:layout_centerVertical="true" >  
  23.   
  24.             <!-- 箭头 -->  
  25.   
  26.             <ImageView  
  27.                 android:id="@+id/head_arrowImageView"  
  28.                 android:layout_width="wrap_content"  
  29.                 android:layout_height="wrap_content"  
  30.                 android:layout_gravity="center"  
  31.                 android:contentDescription="@string/app_name"  
  32.                 android:src="@drawable/arrow" />  
  33.   
  34.             <!-- 进度条 -->  
  35.   
  36.             <ProgressBar  
  37.                 android:id="@+id/head_progressBar"  
  38.                 style="?android:attr/progressBarStyleSmall"  
  39.                 android:layout_width="wrap_content"  
  40.                 android:layout_height="wrap_content"  
  41.                 android:layout_gravity="center"  
  42.                 android:indeterminateDrawable="@drawable/progressbar_bg"  
  43.                 android:visibility="gone" />  
  44.         </FrameLayout>  
  45.   
  46.         <!-- 提示、最近更新 -->  
  47.   
  48.         <LinearLayout  
  49.             android:layout_width="wrap_content"  
  50.             android:layout_height="wrap_content"  
  51.             android:layout_centerHorizontal="true"  
  52.             android:gravity="center_horizontal"  
  53.             android:orientation="vertical" >  
  54.   
  55.             <!-- 提示 -->  
  56.   
  57.             <TextView  
  58.                 android:id="@+id/head_tipsTextView"  
  59.                 android:layout_width="wrap_content"  
  60.                 android:layout_height="wrap_content"  
  61.                 android:text="@string/pull_to_refresh_pull_label"  
  62.                 android:textColor="@color/pull_refresh_textview"  
  63.                 android:textSize="20sp" />  
  64.   
  65.             <!-- 最近更新 -->  
  66.   
  67.             <TextView  
  68.                 android:id="@+id/head_lastUpdatedTextView"  
  69.                 android:layout_width="wrap_content"  
  70.                 android:layout_height="wrap_content"  
  71.                 android:text="@string/pull_to_refresh_refresh_lasttime"  
  72.                 android:textColor="@color/gold"  
  73.                 android:textSize="10sp" />  
  74.         </LinearLayout>  
  75.     </RelativeLayout>  
  76.   
  77. </LinearLayout>  



listfooter_more.xml
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="fill_parent"  
  4.     android:layout_height="fill_parent"  
  5.     android:gravity="center_horizontal"  
  6.     android:orientation="horizontal"  
  7.     android:padding="15dp"  
  8.      >  
  9.   
  10.     <ProgressBar  
  11.         android:id="@+id/pull_to_refresh_progress"  
  12.         style="@android:style/Widget.ProgressBar.Small.Inverse"  
  13.         android:layout_width="wrap_content"  
  14.         android:layout_height="wrap_content"  
  15.         android:gravity="center"  
  16.         android:indeterminate="true"  
  17.         android:visibility="gone" >  
  18.     </ProgressBar>  
  19.   
  20.     <TextView  
  21.         android:id="@+id/load_more"  
  22.         android:layout_width="wrap_content"  
  23.         android:layout_height="wrap_content"  
  24.         android:layout_marginLeft="10.0dp"  
  25.         android:gravity="center"  
  26.         android:text="@string/more_data"  
  27.         android:textColor="@color/black" >  
  28.     </TextView>  
  29.   
  30. </LinearLayout>  


MainActivity.java

  1. package com.example.uitest;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. import android.app.Activity;  
  7. import android.content.Context;  
  8. import android.graphics.BitmapFactory;  
  9. import android.os.Bundle;  
  10. import android.os.Handler;  
  11. import android.util.Log;  
  12. import android.view.LayoutInflater;  
  13. import android.view.Menu;  
  14. import android.view.View;  
  15. import android.view.ViewGroup;  
  16. import android.widget.AdapterView;  
  17. import android.widget.AdapterView.OnItemClickListener;  
  18. import android.widget.BaseAdapter;  
  19. import android.widget.ImageView;  
  20. import android.widget.TextView;  
  21.   
  22. import com.example.uitest.model.AppInfo;  
  23. import com.example.uitest.view.CustomListView;  
  24. import com.example.uitest.view.CustomListView.OnLoadListener;  
  25. import com.example.uitest.view.CustomListView.OnRefreshListener;  
  26.   
  27. public class MainActivity extends Activity {  
  28.       
  29.     private static final String TAG = MainActivity.class.getSimpleName();  
  30.   
  31.     private static final int LOAD_DATA_FINISH = 10;  
  32.   
  33.     private static final int REFRESH_DATA_FINISH = 11;  
  34.       
  35.     private List<AppInfo> mList = new ArrayList<AppInfo>();  
  36.     private CustomListAdapter mAdapter;  
  37.     private CustomListView mListView;  
  38.     private int count = 10;  
  39.     private Handler handler = new Handler(){  
  40.         public void handleMessage(android.os.Message msg) {  
  41.             switch (msg.what) {  
  42.             case REFRESH_DATA_FINISH:  
  43.                   
  44.                 if(mAdapter!=null){  
  45.                     mAdapter.notifyDataSetChanged();  
  46.                 }  
  47.                 mListView.onRefreshComplete();  //下拉刷新完成  
  48.                 break;  
  49.             case LOAD_DATA_FINISH:  
  50.                 if(mAdapter!=null){  
  51.                     mAdapter.notifyDataSetChanged();  
  52.                 }  
  53.                 mListView.onLoadComplete(); //加载更多完成  
  54.                 break;  
  55.             default:  
  56.                 break;  
  57.             }  
  58.         };  
  59.     };  
  60.       
  61.     @Override  
  62.     protected void onCreate(Bundle savedInstanceState) {  
  63.         super.onCreate(savedInstanceState);  
  64.         setContentView(R.layout.activity_main);  
  65.           
  66.         buildAppData();  
  67.           
  68.         mAdapter = new CustomListAdapter(this);  
  69.         mListView = (CustomListView) findViewById(R.id.mListView);  
  70.         mListView.setAdapter(mAdapter);  
  71.           
  72.         mListView.setonRefreshListener(new OnRefreshListener() {  
  73.               
  74.             @Override  
  75.             public void onRefresh() {  
  76.                 //TODO 下拉刷新  
  77.                 Log.e(TAG, "onRefresh");  
  78.                 loadData(0);  
  79.             }  
  80.         });  
  81.           
  82.         mListView.setonLoadListener(new OnLoadListener() {  
  83.               
  84.             @Override  
  85.             public void onLoad() {  
  86.                 //TODO 加载更多  
  87.                 Log.e(TAG, "onLoad");  
  88.                 loadData(1);  
  89.             }  
  90.         });  
  91.           
  92.         mListView.setOnItemClickListener(new OnItemClickListener() {  
  93.   
  94.             @Override  
  95.             public void onItemClick(AdapterView<?> parent, View view,  
  96.                     int position, long id) {  
  97.   
  98.                 Log.e(TAG, "click position:" + position);  
  99.   
  100.             }  
  101.   
  102.         });  
  103.     }  
  104.       
  105.     public void loadData(final int type){  
  106.         new Thread(){  
  107.             @Override  
  108.             public void run() {  
  109.                   
  110.                 for(int i=count;i<count+10;i++){  
  111.                     AppInfo ai = new AppInfo();  
  112.   
  113.                     ai.setAppIcon(BitmapFactory.decodeResource(getResources(),  
  114.                             R.drawable.ic_launcher));  
  115.                     ai.setAppName("应用Demo_" + i);  
  116.                     ai.setAppVer("版本: " + (i % 10 + 1) + "." + (i % 8 + 2) + "."  
  117.                             + (i % 6 + 3));  
  118.                     ai.setAppSize("大小: " + i * 10 + "MB");  
  119.   
  120.                     mList.add(ai);  
  121.                 }  
  122.                 count += 10;  
  123.                   
  124.                 try {  
  125.                     Thread.sleep(300);  
  126.                 } catch (InterruptedException e) {  
  127.                     e.printStackTrace();  
  128.                 }  
  129.                   
  130.                 if(type==0){    //下拉刷新  
  131. //                  Collections.reverse(mList); //逆序  
  132.                     handler.sendEmptyMessage(REFRESH_DATA_FINISH);  
  133.                 }else if(type==1){  
  134.                     handler.sendEmptyMessage(LOAD_DATA_FINISH);  
  135.                 }  
  136.                   
  137.             }  
  138.         }.start();  
  139.     }  
  140.       
  141.     /** 
  142.      * 初始化应用数据 
  143.      */  
  144.     private void buildAppData() {  
  145.         for (int i = 0; i < 10; i++) {  
  146.             AppInfo ai = new AppInfo();  
  147.   
  148.             ai.setAppIcon(BitmapFactory.decodeResource(getResources(),  
  149.                     R.drawable.ic_launcher));  
  150.             ai.setAppName("应用Demo_" + i);  
  151.             ai.setAppVer("版本: " + (i % 10 + 1) + "." + (i % 8 + 2) + "."  
  152.                     + (i % 6 + 3));  
  153.             ai.setAppSize("大小: " + i * 10 + "MB");  
  154.   
  155.             mList.add(ai);  
  156.         }  
  157.     }  
  158.   
  159.     @Override  
  160.     public boolean onCreateOptionsMenu(Menu menu) {  
  161.         // Inflate the menu; this adds items to the action bar if it is present.  
  162.         getMenuInflater().inflate(R.menu.main, menu);  
  163.         return true;  
  164.     }  
  165.       
  166.     public class CustomListAdapter extends BaseAdapter {  
  167.   
  168.         private LayoutInflater mInflater;  
  169.   
  170.         public CustomListAdapter(Context context) {  
  171.             mInflater = LayoutInflater.from(context);  
  172.         }  
  173.   
  174.         @Override  
  175.         public int getCount() {  
  176.             return mList.size();  
  177.         }  
  178.   
  179.         @Override  
  180.         public Object getItem(int arg0) {  
  181.             return mList.get(arg0);  
  182.         }  
  183.   
  184.         @Override  
  185.         public long getItemId(int position) {  
  186.             return position;  
  187.         }  
  188.   
  189.         @Override  
  190.         public View getView(int position, View convertView, ViewGroup parent) {  
  191.             if (getCount() == 0) {  
  192.                 return null;  
  193.             }  
  194.   
  195.             ViewHolder holder = null;  
  196.             if (convertView == null) {  
  197.                 convertView = mInflater.inflate(R.layout.list_item, null);  
  198.   
  199.                 holder = new ViewHolder();  
  200.                 holder.ivImage = (ImageView) convertView  
  201.                         .findViewById(R.id.ivIcon);  
  202.                 holder.tvName = (TextView) convertView  
  203.                         .findViewById(R.id.tvName);  
  204.                 holder.tvVer = (TextView) convertView.findViewById(R.id.tvVer);  
  205.                 holder.tvSize = (TextView) convertView  
  206.                         .findViewById(R.id.tvSize);  
  207.                 convertView.setTag(holder);  
  208.             } else {  
  209.                 holder = (ViewHolder) convertView.getTag();  
  210.             }  
  211.   
  212.             AppInfo ai = mList.get(position);  
  213.             holder.ivImage.setImageBitmap(ai.getAppIcon());  
  214.             holder.tvName.setText(ai.getAppName());  
  215.             holder.tvVer.setText(ai.getAppVer());  
  216.             holder.tvSize.setText(ai.getAppSize());  
  217.   
  218.             return convertView;  
  219.         }  
  220.     }  
  221.   
  222.     public static class ViewHolder {  
  223.         private ImageView ivImage;  
  224.         private TextView tvName;  
  225.         private TextView tvVer;  
  226.         private TextView tvSize;  
  227.   
  228.     }  
  229. }  


list_item.xml

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout   
  3.     xmlns:android="http://schemas.android.com/apk/res/android"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="wrap_content" >  
  6.   
  7.     <ImageView  
  8.         android:id="@+id/ivIcon"  
  9.         android:layout_width="wrap_content"  
  10.         android:layout_height="wrap_content"  
  11.         android:contentDescription="@string/image_desc"  
  12.         android:src="@drawable/ic_launcher" />  
  13.   
  14.     <LinearLayout  
  15.         android:id="@+id/appInfo"  
  16.         android:layout_width="match_parent"  
  17.         android:layout_height="wrap_content"  
  18.         android:layout_marginLeft="5dip"  
  19.         android:layout_toRightOf="@id/ivIcon"  
  20.         android:orientation="vertical" >  
  21.   
  22.         <TextView  
  23.             android:id="@+id/tvName"  
  24.             android:layout_width="wrap_content"  
  25.             android:layout_height="wrap_content"  
  26.             android:text="@string/name"  
  27.             android:textColor="#000000"  
  28.             android:textSize="16sp" />  
  29.   
  30.         <TextView  
  31.             android:id="@+id/tvVer"  
  32.             android:layout_width="wrap_content"  
  33.             android:layout_height="wrap_content"  
  34.             android:text="@string/ver"  
  35.             android:textColor="#666666"  
  36.             android:textSize="13sp" />  
  37.   
  38.         <TextView  
  39.             android:id="@+id/tvSize"  
  40.             android:layout_width="wrap_content"  
  41.             android:layout_height="wrap_content"  
  42.             android:text="@string/size"  
  43.             android:textColor="#666666"  
  44.             android:textSize="13sp" />  
  45.     </LinearLayout>  
  46.   
  47.     <Button  
  48.         android:id="@+id/btnClick"  
  49.         android:layout_width="80dip"  
  50.         android:layout_height="wrap_content"  
  51.         android:layout_alignParentRight="true"  
  52.         android:layout_centerVertical="true"  
  53.         android:focusable="false"  
  54.         android:text="@string/mgr"  
  55.         android:textColor="#000000"  
  56.         android:textSize="16sp" />  
  57.   
  58. </RelativeLayout>  



工程下载地址:http://download.csdn.net/detail/fx_sky/5646017



2018-11-05 23:07:23 weixin_33642281 阅读数 31
  • 通往Android的神奇之旅

    本套Android课程,大家可以一路学习基本控件,控件,网络,自定义View,地图,推送,后端云,项目实战2048游戏等等,通过本套课程,相信大家可以基本掌握Android入门知识。

    82273 人正在学习 去看看 刘桂林

Android缓存策略

当程序第一次从网络加载图片后,就将其缓存到存储设备和内存中。当下次程序使用图片时,会先从内存中获取,如果内存中没有再从存储设备中获取,如果存储设备中也没有,则从网络上下载这张图片。

从内存中加载图片比从存储设备中加载图片要快,可以提高程序的效率且节约流量。

LRU(Least Recently Used)是一种常用的缓存算法。即近期最少使用算法。
LRU核心思想是当缓存满时,优先淘汰那些近期最少使用的缓存对象。

LruCache

LruCache是Android3.1提供的缓存类,Android官方API。可以用support-v4兼容包中提供的LruCache兼容早期Android版本。
LruCache是一个泛型类,内部采用一个LinkedHashMap以强引用的方式存储缓存的对象。LruCache线程安全。

DiskLruCache

DiskLruCache用于实现存储设备缓存。DiskLruCache非Android官方API。

glide

提供:bumptech
地址:https://github.com/bumptech/glide
当前版本:4.8.0
Glide是一个快速高效的Android图片加载库。
Glide 支持拉取,解码和展示视频快照,图片,和GIF动画。
Glide使用的是一个定制化的基于HttpUrlConnection的栈,但同时也提供了与Google Volley和Square OkHttp快速集成的工具库。

链式调用

	ImageView imageView = findViewById(R.id.test);
    ImgurGlide.with(getApplicationContext())
            .load(imageUrl)
            .into(imageView);

配合生命周期使用

Glide在加载资源的时候,如果是在Activity、Fragment上进行,当Activity、Fragment等组件进入不可见,或者已经销毁的时候,Glide 会停止加载资源。
如果不在Activity/Fragment上进行,则根据Application的生命周期,在应用程序关闭的时候终止加载。

缓存设置

  1. 某些情形下,你可能希望只要图片不在缓存中则加载直接失败(比如省流量模式)。如下设置则如果图片在内存缓存或在磁盘缓存中,它会被展示出来。否则只要这个选项被设置为 true ,这次加载会视同失败。
GlideApp.with(fragment)
  .load(url)
  .onlyRetrieveFromCache(true)
  .into(imageView);
  1. 如果你想跳过磁盘和/或内存缓存(比如图片验证码),如下设置:
GlideApp.with(fragment)
  .load(url)
  .diskCacheStrategy(DiskCacheStrategy.NONE)
  .skipMemoryCache(true)
  .into(view);

fresco

提供:facebook
地址:https://github.com/facebook/fresco
当前版本:1.10.0

添加依赖

build.gradle文件中添加依赖

dependencies {
  // 其他依赖
  compile 'com.facebook.fresco:fresco:1.5.0'
}

下面的依赖需要根据需求添加:

dependencies {

  // 支持 GIF 动图,需要添加
  compile 'com.facebook.fresco:animated-gif:1.5.0'

  // 支持 WebP (静态图+动图),需要添加
  compile 'com.facebook.fresco:animated-webp:1.5.0'
  compile 'com.facebook.fresco:webpsupport:1.5.0'

  // 仅支持 WebP 静态图,需要添加
  compile 'com.facebook.fresco:webpsupport:1.5.0'

  // 添加Android支持包(你可能已经添加了该依赖或者类似的依赖包)
  compile 'com.android.support:support-core-utils:25.3.1'
}

初始化

在Application中调用Fresco.initialize初始化Fresco类。

	Fresco.initialize(this);

SimpleDraweeView

使用com.facebook.drawee.view.SimpleDraweeView展示图片。

	<com.facebook.drawee.view.SimpleDraweeView
    android:id="@+id/my_image_view"
    android:layout_width="130dp"
    android:layout_height="130dp"
    fresco:placeholderImage="@drawable/my_drawable"
  />
Uri uri = Uri.parse("https://raw.githubusercontent.com/facebook/fresco/gh-pages/static/logo.png");
SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
draweeView.setImageURI(uri);

glide和fresco对比

glide优点:

  1. 生命周期集成。
  2. 复用和主动回收bitmap,减少系统回收压力。
  3. 缓存策略灵活,多种规格缓存。

glide缺点:

  1. Java heap比Fresco高。

fresco优点:

  1. 在5.0以下系统(最低2.3)对bitmap的加载效率高,Fresco将图片放到一个特别的内存区域(Ashmem区)。
  2. 大大减少OOM(在更底层的Native层对OOM进行处理,图片将不再占用App的内存)。

fresco缺点:

  1. 包较大(2~3M)
  2. 用法复杂
没有更多推荐了,返回首页