Akita (A Kit of Android) v1.2 User Manual
Copyright (c) 1999-2101 Alibaba.com
开发者:杨喆 zhe.yangz@alibaba-inc.com
7.1 缩放拖动图片组件
PinchZoomImageView
akita是一套Android快速开发框架及组件库,建立在Android 官方SDK基础之上,涵盖了网络IO、API调用动态代理注解、Json自动解析、远程图片组件、异步调用组件、通用Cache组件等方面的有效实现,是多年Android App开发所积累下来的有效代码的重构组织。
使用akita库开发Android App,可以让你用更少的代码、更短的时间完成相同的功能和界面,并且稳定、高效、可读性良好。
akita被设计为一个通用的快速开发库,用来支持80%以上的Android App的开发,主要分为NetworkIO、API Framework、Cache、Util、Widgt、Async、UITemplate等模块。
图 1-1 一般Android App架构图
以AliExpress Android App为例,一般的Android App架构如图1-1所示。
通过动态代理和注解等Java高级特性,支持“定义即实现”的服务侧接口调用方式。
比如要实现一个Taobao的获取商品详情的API接口:
http://gw.api.taobao.com/router/rest?sign=F05F6769F2F4C22D679C70CE054D7798×tamp=2012-05-14+17%3A01%3A05&v=2.0&app_key=12129701&method=taobao.item.get&partner_id=top-apitools&format=json&num_iid=15824668129&fields=detail_url,num_iid,title,nick,type,cid,seller_cids,desc
Json返回如下:
通常在Android上实现这样一个接口,需要调用网络访问代码,判断返回状态,获取返回的Response,定义一些Parser,手写很多json解析代码,然后赋值到新创建的pojo对象中。在这个过程中的很多代码都是重复冗余的。
而现在,akita通过提供的统一API调用框架,减少了冗余代码,最大程度简化实现过程。使用akita进行调用,只需定义如下代码:
public
interface TopApi {
@AkGET
@AkAPI(url="http://gw.api.taobao.com/router/rest")
ItemGetResult
taobao_item_get(
@AkParam("sign") String sign,
@AkParam("timestamp") String timestamp,
@AkParam("v") String v,
@AkParam("app_key") int app_key,
@AkParam("method") String method,
@AkParam("partner_id") String partner_id,
@AkParam("format") String format,
@AkParam("num_iid") long num_iid,
@AkParam("fields") String fields
) throws AkInvokeException,
AkServerStatusException;
}
定义pojo对象,或者可以考虑建立统一的业务模型:
public class ItemGetResult {
public ItemGetResponse item_get_response;
public static class ItemGetResponse {
public Item item;
public static class Item {
public int cid;
public String desc;
public String detail_url;
public String nick;
public long num_iid;
public String seller_cids;
public String title;
public String type;
}
}
}
然后,就可以在业务逻辑层随时调用该API了。含异常处理的调用API代码如下:
TopApi topApi = ProxyFactory.getProxy(TopApi.class);
try {
ItemGetResult result = topApi.taobao_item_get(
"F05F6769F2F4C22D679C70CE054D7798", "2012-05-14+17%3A01%3A05",
"2.0", 12129701, "taobao.item.get", "top-apitools", "json",
15824668129L, "detail_url,num_iid,title,nick,type,cid,seller_cids,desc");
// ...
} catch (AkInvokeException e) {
e.printStackTrace(); //defaults
} catch (AkServerStatusException e) {
e.printStackTrace(); //defaults
}
“定义即实现”,通过以上的例子,可以看到,只需定义API方法和参数名称、业务模型等类和方法,就能通过ProxyFactory自动实现该API调用类。
支持POST、GET、PUT、DELETE等请求方式,使用@AkGET、@AkPOST等注解,标识了特定API使用特定请求方式。
一般的URL样式为:
http://server.com/apiname1?param1=value1¶m2=value2
对应的接口定义代码:
public
interface TestApi {
@AkGET
@AkAPI(url="http://server.com/apiname1")
String method1(
@AkParam("param1") String param1,
@AkParam("param2") String param2
) throws AkInvokeException,
AkServerStatusException;
}
传参还可以另外一种形式简便定义:
public
interface TestApi {
@AkGET
@AkAPI(url="http://server.com/apiname1")
String method1(
@AkParam("$paramMap") Map<String, String> paramMap,
) throws AkInvokeException,
AkServerStatusException;
}
在使用时,对paramMap传入对应参数后即可调用。
对于文件上传可以加入如下参数:
@AkParam("$filesToSend") Map<String, File> filesToSend,
另外,对于特殊的URL样式(参数为url中拼接)如:
http://server.com/getItem/{itemId}.json
// 例如活动itemid为110023的item内容
http://server.com/getItem/110023.json
akita支持定义接口如下:
public
interface TestApi {
@AkGET
@AkAPI(url="http://server.com/getItem/{itemId}.json")
String method1(
@AkParam("itemId") String itemId,
) throws AkInvokeException,
AkServerStatusException;
}
对于返回的json格式中有固定报头及调用状态等信息的,如下面的json返回格式:
业务内容会在body中出现,而返回格式中始终会有head部分,存放本次调用的成功与否的代码code和对于消息体message。
那么,可以使用如下代码定义该接口:
// 使用OceanParam2Result<T>作为返回值
public
interface TestApi {
@AkGET
@AkAPI(url="http://server.com/getTouch)
OceanParam2Result<GetTouchResult>
method1(
@AkParam("itemId") String itemId,
) throws AkInvokeException,
AkServerStatusException;
}
// 定义对应于业务内容的业务模型
public
static class GetTouchResult {
public String sessionId;
public String country;
}
其中,OceanParam2Result<T>可以预先一次性定义如下:
public class OceanParam2Result<T> {
private static final String
TAG = "OceanParam2Result<T>";
public Head head;
public static class Head {
public String message;
public String code;
}
public JsonNode body;
@SuppressWarnings("unchecked")
public T getBody(Class<T> entityClass) throws AkInvokeException{
if(body != null && body.size()>0){
try {
T t = JsonMapper.node2pojo(body, entityClass);
try {
Method method = entityClass.getDeclaredMethod("fulFill");
method.setAccessible(true);
t = (T) method.invoke(t);
} catch (SecurityException e) {
Log.v(TAG, "Security in fulFill");
} catch (NoSuchMethodException e) {
Log.v(TAG, "NoSuchMethod of fulFill.");
}
return t;
} catch (JsonProcessingException e) {
Log.e(TAG, body.toString()); // log can print the error return-string
throw new AkInvokeException(AkInvokeException.CODE_JSONPROCESS_EXCEPTION,
e.getMessage(), e);
} catch (IOException e) {
throw new AkInvokeException(AkInvokeException.CODE_IO_EXCEPTION,
e.getMessage(), e);
} catch (InvocationTargetException ite) {
throw new AkInvokeException(AkInvokeException.CODE_FULFILL_INVOKE_EXCEPTION,
ite.getMessage(), ite);
} catch (IllegalAccessException e) {
throw new AkInvokeException(AkInvokeException.CODE_FULFILL_INVOKE_EXCEPTION,
e.getMessage(), e);
}
}
return null;
}
}
这样,每次调用如下代码就可以得到业务内容结果:
GetTouchResult result = oceanParam2Result.getBody(GetTouchResult.class);
同时,对于head的读取和检查也可以通过oceanParam2Result的调用实现:
assertTrue(“200”.oceanParam2Result.head.code);
assertNotNull(oceanParam2Result.head.message);
在HttpInvoker中封装了基于HttpClient的网络调用,并且支持图片上传。HttpInvoker中的调用如下:
public static String get(String url);
public static String post(String url, ArrayList<NameValuePair> params);
public static String put(String url, HashMap<String, String> map);
public static String delete(String url);
public static String postWithFilesUsingURLConnection(
String actionUrl, ArrayList<NameValuePair> params, Map<String, File> files);
public static Bitmap getBitmapFromUrl(String imgUrl, int inSampleSize);
public static
Bitmap getImageFromUrl(String imgUrl, int inSampleSize);
使用线程安全的HttpClient支持多线程调用,支持传输数据gzip自动解压缩,减少网络流量的耗费。
akita中定义的异常有:AkServerStatusException, AkInvokeException。
以上异常都继承于AkException,其中Ak开头的为Akita库定义,在开发上层SDK或应用时可以基于AkException定义上层异常,比如可以以Ae开头,定义AE SDK中使用的异常。
n akita异常说明
AkServerStatusException:Akita实现的API在HTTP(S)访问时得到的返回状态错误,会引起此异常。
AkInvokeException:Akita实现的API调用,除了引起HTTP返回状态异常外的其他情况。如网络不通、超时等。
n 上层自定义异常举例
AeResultException:处理AE SDK中的业务Api调用返回的业务层面的状态,即ws-mobile接口返回的Head的code和message。
AeNeedLoginException:需要登录后使用的Api,在未登录时,抛出此异常。
n 异常处理框架
ExceptionHandlerExecutor:Akita库中实现的统一异常处理执行器。
AeExceptionHandler:具体处理的策略可以自定义实现。AeExceptionHandler继承自Akita库中BasicExceptionHandler,BasicExceptionHandler实现ExceptionHandler接口,接口定义了public void handle(AkException e)方法。
n 使用实例
一般在应用或SDK中调用API方法的默认异常处理例子:
try {
// Api 调用 code here.
} catch (AkException e) {
ExceptionHandlerExecutor.execute(new AeExceptionHandler(mContext), e);
}
其中AeExceptionHandler继承于akita中的BasicExceptionHandler,一种默认实现如下:
public class AeExceptionHandler extends BasicExceptionHandler {
private static final String
TAG = "AeExceptionHandler";
public AeExceptionHandler(Context
c) {
super(c); }
@Override
public void
handle(AkException e) { Log.e(TAG, e.toString, e); }
}
图1-2 Cache框架
目前akita中对于Cache分为三种类型:MemCache、FilesCache、SimpleCache,开发者可以根据实际需求选择使用。
MemCache:特点是存储在内存中,KV都可以存复杂对象。目前有使用LruCache(only Android 3+),和使用软引用SoftReference实现。
FilesCache:特点是持久存储在文件(夹)结构中,K固定为String,V可以存复杂对象(需定义序列化过程)。目前有实现在SD卡Cache空间中,以文件夹结构方式。
SimpleCache:特点是简单存储,KV都只能为String。可以持久或临时性存储。目前以Sqlite存储实现。
App上的绝大部分Cache需求可以通过以上三种Cache实现,但根据不同需求可能需要再增加合适的Impl实现。
akita中使用akCacheManager来统一管理、获取各种类型Cache。
远程图片组件、简单异步调用组件、积累的适配器、对话框等组件。
图1-3 丰富并不断积累的工具类
本章包含akita库基本的使用步骤,方便你快速入门。
n 依赖包
Jackson解析库:akita1.0依赖Jackson 1.9.0的core和mapper包。Jackson libs已经在默认的akita包中包含,但是为了节省空间已经你本来就引入了Jackson包的情况下,可以使用剥离Jackson libs的akita包。
图2-1 依赖的Jackson版本
Android SDK:在Android 4.0 SDK上测试稳定,akita库设计支持2.2+的sdk,但akita 1.0版本由于时间的关系,只验证过在Android 4.0 SDK上的稳定和正确性。
Eclipse + ADT
或者
IDEA IntelliJ (with Android support) 推荐
(以IDEA IntelliJ为例)
New Module => Create module from scratch => Android Module
图2-2 Android Module
Next => Next => Finish.
在Project中选中刚刚新建项目的名称,
右键 => Open Module Settings (或按F4键) => 进入Project Structure对话框,
然后如下图所示添加akita包
图2-3 添加akita包
或者如果你直接导入了akita1.0项目的话,选择Module Dependency,然后添加akita 1.0项目作为依赖。
在项目自动创建的MainActivity中实现一个测试API接口,这里使用淘宝TOP平台的获取商品详情的接口taobao.item.get,代码如下:
public
class MainActivity extends AbsBottomTabActivity {
@Override
public void onCreate(Bundle
savedInstanceState) {
super.onCreate(savedInstanceState); // default Templates
TopApi topApi = ProxyFactory.getProxy(TopApi.class);
try
{
ItemGetResult result = topApi.taobao_item_get(
"F05F6769F2F4C22D679C70CE054D7798",
"2012-05-14+17%3A01%3A05",
"2.0", 12129701, "taobao.item.get",
"top-apitools", "json",
15824668129L,
"detail_url,num_iid,title,nick,type,cid,seller_cids,desc");
Toast.makeText(this, result.item_get_response.item.desc,
Toast.LENGTH_LONG).show();
// ...
}
catch (AkInvokeException e) {
e.printStackTrace(); //defaults
}
catch (AkServerStatusException e) {
e.printStackTrace();
//defaults
}
}
public interface TopApi {
@AkGET
@AkAPI(url="http://gw.api.taobao.com/router/rest")
ItemGetResult taobao_item_get(
@AkParam("sign") String sign,
@AkParam("timestamp") String timestamp,
@AkParam("v") String v,
@AkParam("app_key") int app_key,
@AkParam("method") String method,
@AkParam("partner_id") String partner_id,
@AkParam("format") String format,
@AkParam("num_iid") long num_iid,
@AkParam("fields") String fields
)
throws AkInvokeException, AkServerStatusException;
}
}
以上,即已经实现了此API接口及它的调用。
akita其他的功能特性将分别在以下章节详述。同时,可以下载akita-samples-1.0领略全部功能的演示。
n 说明
一般情况下,这部分网络IO调用不会被开发者直接使用,它们已经自动集成进远程API框架中作为网络的基础组件。但是,在一些特殊的情况下,开发者可能必须直接使用。
那就以诸如以下方式直接使用:
String response = HttpInvoker.get(url);
n 网络接口使用
// http GET
public static String get(String url);
// http POST
public static String post(String url, ArrayList<NameValuePair> params);
// to be impl.
public static String put(String url, HashMap<String, String> map);
// to be impl.
public static String delete(String url);
// with Files
public static String postWithFilesUsingURLConnection(
String actionUrl, ArrayList<NameValuePair> params, Map<String, File> files);
// version 2: remoteimageview download impl, use byte[] to decode. Recomanded.
public static Bitmap getBitmapFromUrl(String imgUrl, int inSampleSize);
// version 1: remoteimageview download impl, use InputStream to decode.
public static
Bitmap getImageFromUrl(String imgUrl, int inSampleSize);
n 统一的异常处理
akita中定义的异常有:AkServerStatusException, AkInvokeException。
以上异常都继承于AkException,其中Ak开头的为Akita库定义,在开发上层SDK或应用时可以基于AkException定义上层异常,比如可以以Ae开头,定义AE SDK中使用的异常。
n akita异常说明
AkServerStatusException:Akita实现的API在HTTP(S)访问时得到的返回状态错误,会引起此异常。
AkInvokeException:Akita实现的API调用,除了引起HTTP返回状态异常外的其他情况。如网络不通、超时等。
n 上层自定义异常举例
AeResultException:处理AE SDK中的业务Api调用返回的业务层面的状态,即ws-mobile接口返回的Head的code和message。
AeNeedLoginException:需要登录后使用的Api,在未登录时,抛出此异常。
n 异常处理框架
ExceptionHandlerExecutor:Akita库中实现的统一异常处理执行器。
AeExceptionHandler:具体处理的策略可以自定义实现。AeExceptionHandler继承自Akita库中BasicExceptionHandler,BasicExceptionHandler实现ExceptionHandler接口,接口定义了public void handle(AkException e)方法。
n 使用实例
一般在应用或SDK中调用API方法的默认异常处理例子:
try {
// Api 调用 code here.
} catch (AkException e) {
ExceptionHandlerExecutor.execute(new AeExceptionHandler(mContext), e);
}
其中AeExceptionHandler继承于akita中的BasicExceptionHandler,一种默认实现如下:
public class AeExceptionHandler extends BasicExceptionHandler {
private static final String
TAG = "AeExceptionHandler";
public AeExceptionHandler(Context
c) {
super(c); }
@Override
public void
handle(AkException e) { Log.e(TAG, e.toString, e); }
}
见1.2.1 - 1.2.4的描述。
n 组件位置
com.alibaba.akita.cache.MemCache<K, V>
n 接口定义
public interface MemCache<K, V> {
public V get(K key);
public V put(K key, V value);
public V remove(K key);
/**
* Clear all kvs of this MemCache
*/
public void clear();
/**
* Get a snapshot of cache
*/
public Map<K, V> snapshot();
}
n 使用说明
目前有两种MemCache,分别可以用AkCacheManager的静态方法创建。
分别为LruMemCache(必须在API Level >= 12的设备上使用,因为使用的是基于android.util.LruCache的封装),及SoftRefMemCache(这个不常使用,目前使用在RemoteImageView的内存级别图片本地缓存的实现中)。
n 使用方法举例
// 创建一个20个元素大小的memCache,LRU算法更新
MemCache<String, String> memCache = AkCacheManager.newMemLruCache(20);
// 软引用memCache
MemCache<String, String> memCache = AkCacheManager.newMemSoftRefCache();
n 组件位置
com.alibaba.akita.cache.SimpleCache
n 接口定义
public interface SimpleCache {
public String get(String key);
/**
* get Latest items, max to num
* @param num
* @return
*/
public ArrayList<String> getLatest(int num);
public String put(String key, String value);
public String remove(String key);
public void removeAll();
/**
* Close the db and sth. else.
* @return
*/
public void close();
}
n 使用说明
SimpleCache适合于kv对的持久性存储,采用SQLite作为底层数据库实现,提供上层kv调用接口。数据文件存储在系统区相关app对应的 private存储区的.db文件中。根据应用场景不同分为Cache和AppData两种。
n 使用举例
// “defaulttable” 表中,数据文件为simplecache.db,默认保留24小时
SimpleCache simpleCache1 = AkCacheManager.getSimpleCache(context);
simpleCache1.close();
// tagName 表中, 数据文件为simplecache.db,默认保留24小时
SimpleCache simpleCache2 = AkCacheManager.getSimpleCache(context, tagName);
simpleCache2.close();
// tagName 表中, 数据文件为simplecache.db, 保留hours小时
SimpleCache simpleCache3 = AkCacheManager.getSimpleCache(context, tagName, hours);
simpleCache3.close();
// “defaulttable”表中,数据文件为appdata.db,默认保留365天
SimpleCache appData1 = AkCacheManager.getAppData(context);
appData1.close();
// tagName 表中,数据文件为appdata.db,默认保留365天
SimpleCache appData2 = AkCacaheManager.getAppData(context, tagName);
appData2.close();
n 组件位置
com.alibaba.akita.cache.FilesCache<V>
n 接口定义
public interface FilesCache<V> {
public V get(String key);
/**
* @param key
* @param value if value is null, no effect and return null
* @return oldValue or null if has no oldValue
*/
public V put(String key, V value);
public V remove(String key);
/**
* evict cache data according to the cache size set.
* @return
*/
public void evict();
/**
* Clear all this cache data
* Note: this method maybe Time-consuming, you may run it async
*/
public void clearCache();
/**
* get current cache size in MB
* @return
*/
public double getCacheCurrentSizeMB();
/**
* set normal cache size limit in MB
* @param cacheSizeInMB
*/
public void setCacheSize(int cacheSizeInMB);
}
n 说明
Widget组件库中的RemoteImageView集成了本地文件系统Cache。
注意:千万不要忘记在权限设置上添加以下行,否则此Cache只能用到内存部分。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
n 使用举例
虽然这主要是给RemoteImageView提供的基础图片文件系统Cache功能,但也可以被外部使用。外部使用图片FilesCache时,通过以下方式获得。
FilesCache<Bitmap> imageCache = AkCacheManager.getImageFilesCache(context);
imageCache.setCacheSize(cacheSizeInMB);
// 执行evict操作,在大于mCacheSize时,会清空一部分Cache数据
imageCache.evict();
// 得到目前存储的文件系统cache大小
imageCache.getCacheCurrentSizeMB();
// 全部清空
imageCache.clearCache();
<com.alibaba.akita.widget.PinchZoomImageView
android:layout_width=”match_parent”
android:layout_height=”match_parent” />
支持图片的多次触摸方式缩放,xy平面移动等。使用方式类似ImageView。
<com.alibaba.akita.widget.RemoteImageView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:akita="http://schemas.alibaba.com/apk/res/akita"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
akita:fadeIn="true"
akita:pinchZoom="true"
/>
n 自定义参数设置
akita:fadeIn=”true|fasle” 启用图片淡入效果
akita:pinchZoom=”true|fasle” 启用图片组件支持缩放、拖动
akita:showProgress=”true|false” 启用载入图片时显示实际下载进度条
akita: imgBoxWidth=”100” 设定图片组件的限制盒宽度
akita: imgBoxHeight=”20” 设定图片组件的限制盒高度
用来标识ViewPager组件的当前页。
n 参考代码
界面定义:
<android.support.v4.view.ViewPager
android:id="@+id/vp_banner"
android:layout_width="fill_parent"
android:layout_height="100dp"
/>
<com.alibaba.akita.widget.CirclePageIndicator
android:id="@+id/cpi_indicator1"
android:padding="3dp"
android:layout_marginBottom="2dp"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
/>
设置代码:
mViewPager = (ViewPager) v.findViewById(R.id.vp_banner);
mViewPager.setAdapter(new BannerPageAdapter());
mIndicator = (CirclePageIndicator) v.findViewById(R.id.cpi_indicator1);
mIndicator.setViewPager(mViewPager);
mIndicator.setFillColor(getResources().getColor(R.color.Orange));
mIndicator.setPageColor(getResources().getColor(R.color.Black));
ViewPager采用v4包中标准实现,一个简单内容适配器代码:
protected class
BannerPageAdapter extends PagerAdapter {
@Override
public Object instantiateItem(ViewGroup container, int position) {
RemoteImageView iv = (RemoteImageView)
getActivity().getLayoutInflater().inflate(R.layout.iv_banner, null);
iv.setImageUrl("http://i03.i.aliimg.com/images/cms/upload/wholesale/"
+
"home_banner/2012/05/childrensday/banner/590x200.jpg");
iv.loadImage();
container.addView(iv, 0);
return iv;
}
@Override
public void destroyItem(ViewGroup container, int position, Object
object) {
container.removeView((RemoteImageView) object);
}
@Override
public int getCount() {
return 3; //defaults
}
@Override
public boolean isViewFromObject(View view, Object o) {
return view == o;
}
}
也可参见UI适配器章节中,8.2.2小结,封装的AkPagerAdapter例子。
或者参加下一节v4兼容包中的ViewPager用法介绍。
类似于Workspace的实现,是google v4兼容包中发布的标准实现组件。
n 使用方法参考
界面定义:
<android.support.v4.view.ViewPager
android:id="@+id/vp1"
android:layout_width="fill_parent"
android:layout_height="fill_parent "
/>
关于其中内容的填充,除了上节中提到的简单适配器实现,还可以用Fragment方式的适配器:
class TestFragmentAdapter extends FragmentPagerAdapter {
protected static final String[] CONTENT =
new String[] { "This", "Is", "A",
"Test", };
private int mCount = CONTENT.length;
public TestFragmentAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
return TestFragment.newInstance(CONTENT[position % CONTENT.length]);
}
@Override
public int getCount() {
return mCount;
}
public void setCount(int count) {
if (count > 0 && count <= 10) {
mCount = count;
notifyDataSetChanged();
}
}
}
其中TestFragment的newInstance关键代码:
public static TestFragment newInstance(String content) {
TestFragment fragment = new TestFragment();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 20; i++) {
builder.append(content).append(" ");
}
builder.deleteCharAt(builder.length() - 1);
fragment.mContent = builder.toString();
return fragment;
}
n 注意
目前,ViewPager与ScrollView可能会在触摸响应处理时产生冲突,如果ViewPager作为子View放置在ScrollView里边的话,可能在onFling等响应事件上产生失败。这时候,一种可能的解决方案是使用ScrollViewFixed代替ScrollView。ScrollViewFixed对拦截的时机进行了处理,代码如下:
public class ScrollViewFixed extends ScrollView {
private GestureDetector mGestureDetector;
View.OnTouchListener mGestureListener;
public ScrollViewFixed(Context context, AttributeSet attrs) {
super(context, attrs); //defaults
mGestureDetector = new GestureDetector(new YScrollDetector());
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev) && mGestureDetector.onTouchEvent(ev);
}
// Return false if we're scrolling in the x direction
class YScrollDetector extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
if(Math.abs(distanceY) > Math.abs(distanceX)) {
return true;
}
return false;
}
}
}
或者
public class ScrollViewFixed extends ScrollView {
private GestureDetector mGestureDetector;
View.OnTouchListener mGestureListener;
public ScrollViewFixed(Context context, AttributeSet attrs) {
super(context, attrs); //defaults
setFadingEdgeLength(0);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev) && (ev.getY()>( 200 -this.getScrollY()));
}
}
n 组件信息
com.alibaba.akita.ui.async.SimpleAsyncTask<T> extends AsyncTask<Integer, Integer, T>
n 说明
SimpleAsyncTask是在AsyncTask基础上做的简化,方便开发者更快更简洁的使用异步框架。会用AsyncTask的人对SimpleAsyncTask相信无需多做说明了,本来在以前我们开发的app中使用了AsyncTask的函数调用形式,后来经过比较和权衡,渐渐不使用函数形式而用SimpleAsyncTask来代替。
SimpleAsycTask将原本的execute方法封装成为了fire(),这个方法很形象的说明了SimpleAsyncTask的用处,就是new出来,然后发射,然后不去管了。
Android API LEVEL >= 11的情况下,AsyncTask的execute()默认是单线程执行的,使用SerialExecutor。要使其在线程池中执行,需要指定THREAD_POOL_EXECUTOR。对此,我们对应封装了fireOnParallel()。
n 举例
Detail页面加载过程
new SimpleAsyncTask<ProductDetail>() {
// 在UI线程中执行,并且在Worker线程开始之前
@Override
protected void onUIBefore() throws AkException {
getView().findViewById(R.id.ll_loading).setVisibility(View.VISIBLE);
}
// 在开启的Worker线程中执行
@Override
protected ProductDetail onDoAsync() throws AkException {
return AEApp.getApp().aeClient.productGetWholeProductDetail(productId);
}
// 在Worker线程执行完之后,在UI线程中执行,入参是Worker中返回的。
// 注意:如果onDoAsync及之前的方法异常中断走入AkException处理流程,此方法将不执行。将跳入onHandleAkException方法中。
@Override
protected void onUIAfter(ProductDetail productDetail) throws AkException {
if (productDetail != null) {
detailFragmentSupport.setProductDetail(productDetail);
doRefreshData();
}
}
// 遇到异常后会跳入此方法
@Override
protected void onHandleAkException(AkException mAkException) {
try{
ExceptionHandlerExecutor.execute(new AeExceptionHandler(getActivity()), mAkException);
} catch (Exception e) {e.printStackTrace();}
try {
getView().findViewById(R.id.ll_fail).setVisibility(View.VISIBLE);
} catch (NullPointerException npe) {}
}
// 此方法在UI线程中执行,肯定会被执行,且在其他方法执行后。
@Override
protected void onUITaskEnd() {
try {
getView().findViewById(R.id.ll_loading).setVisibility(View.GONE);
} catch (NullPointerException npe) {}
}
}.fire();
n 举例
商品列表Adapter
public static class ProductListAdapter
extends AkBaseAdapter<ProductSearchResult.ProductSummary> {
public ProductListAdapter(Context c) {
super(c);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
ViewGroup rl_productlist_productsummary = (ViewGroup) mInflater.inflate(
R.layout.griditem_productlist_productsummary, null);
viewHolder = new ViewHolder();
viewHolder.riv_productsummary_img = (RemoteImageView)
rl_productlist_productsummary.findViewById(R.id.riv_productsummary_img);
viewHolder.riv_productsummary_img.setLocalImage(R.drawable.aliexpress);
viewHolder.tv_productsummary_title = (TextView)
rl_productlist_productsummary.findViewById(R.id.tv_productsummary_title);
viewHolder.tv_productsummary_price = (TextView)
rl_productlist_productsummary.findViewById(R.id.tv_productsummary_price);
viewHolder.tv_productsummary_minorder = (TextView)
rl_productlist_productsummary.findViewById(R.id.tv_productsummary_minorder);
viewHolder.tv_productsummary_sold = (TextView)
rl_productlist_productsummary.findViewById(R.id.tv_productsummary_soldnum);
viewHolder.tv_productsummary_freeshipping = (TextView)
rl_productlist_productsummary.findViewById(R.id.tv_productsummary_freeshipping);
convertView = rl_productlist_productsummary;
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
ProductSearchResult.ProductSummary productSummary = mData.get(position);
ProductUtil.ProductSummaryVO productSummaryVO = ProductUtil.getProductSummaryVO(productSummary);
viewHolder.riv_productsummary_img.setImageUrl(productSummary.img_url);
viewHolder.riv_productsummary_img.loadImage();
viewHolder.tv_productsummary_title.setText(productSummary.subject);
viewHolder.tv_productsummary_price.setText(Html.fromHtml(MessageFormat.format(
mContext.getString(R.string.productsummary_price),
productSummaryVO.price,
productSummaryVO.pieceUnit
)));
viewHolder.tv_productsummary_minorder.setText(MessageFormat.format(
mContext.getString(R.string.productsummary_minorder),
productSummaryVO.minOrder,
productSummaryVO.minOrderUnit
));
viewHolder.tv_productsummary_sold.setText(MessageFormat.format(
mContext.getString(R.string.productsummary_sold),
productSummaryVO.sold
));
if ("y".equals(productSummary.is_free_ship)) {
viewHolder.tv_productsummary_freeshipping.setVisibility(View.VISIBLE);
} else {
viewHolder.tv_productsummary_freeshipping.setVisibility(View.GONE);
}
return convertView; //defaults
}
static class ViewHolder {
public RemoteImageView riv_productsummary_img;
public TextView tv_productsummary_title;
public TextView tv_productsummary_price;
public TextView tv_productsummary_minorder;
public TextView tv_productsummary_sold;
public TextView tv_productsummary_freeshipping;
}
}
封装了PagerAdapter及对象数组存储,配合作为ViewPager的简单适配器使用。
n 相关方法
public int getCount();
public void addItem(final T item);
public void addItem(int idx, final T item);
public void clearItems();
public T getItem(int position);
n 参考代码
适配器简单例程:
protected class PicViewImgsAdapter extends AkPagerAdapter<PicViewItem> {
public PicViewImgsAdapter(Context c) {
super(c);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
RemoteImageView iv = (RemoteImageView)
mInflater.inflate(R.layout.iv_picview, null);
iv.setImageUrl(mData.get(position).imgUrl);
iv.loadImage();
container.addView(iv, 0);
return iv;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((RemoteImageView) object);
}
@Override
public boolean isViewFromObject(View view, Object o) {
return view == o;
}
}
适配器设置代码:
ViewPager
viewPager = (ViewPager) v.findViewById(R.id.vp_imgs);
PicViewImgsAdapter
picViewImgsAdapter = new PicViewImgsAdapter(getActivity());
picViewImgsAdapter.addItem(new
PicViewItem(url));
viewPager.setAdapter(picViewImgsAdapter);
简单Loading提示对话框
n 使用方法
SimpleLoadingDialog simpleLoadingDialog =
SimpleLoadingDialog(context, loadingHintString);
simpleLoadingDialog.setHintProgress(hintProgressString);
simpleLoadingDialog.show();
simpleLoadingDialog.dismiss();
n public static int dp2px(Context context, float dpValue);
dp to px像素转换
n public static int getVerCode(Context context);
得到VersionCode
n public static String getVerName(Context context);
得到VersionName
n public static String getAppName(Context context);
得到AppName
n public static String getDeviceId(Context context);
得到DeviceID,得不到返回null。
n public static void hideIME(Activity context, boolean bHide);
隐藏IME软键盘
n public static void showIMEonDialog(AlertDialog dialog);
设置Dialog显示时跳出软键盘
n public static boolean isPkgInstalled(Context ctx, String packageName);
判断一个Package是否已经安装
n public static boolean isGooglePlayInstalled(Context ctx);
判断GooglePlay是否已经安装
n public static boolean networkStatusOK(final Context context);
网络是否可用,包含了Wifi和2/3G。
n public static void showNetworkFailureDlg(final Activity context);
弹出网络不可用对话框
n public static String getSimpleDatetime(long milliseconds);
简单的默认格式日期时间
n public static String getDefaultDatetime(long milliseconds);
"yyyy-MM-dd HH:mm:ss Z"
n public static String getTimeString(String strTime);
"20100315072741000-0700" => "yyyy-MM-dd HH:mm:ss Z"
对于单元测试的支持,一个测试Case。
n 示例代码
@AkTest("You May Like it 推荐接口")
public void testYouMayLikeIt() {
try {
ArrayList<String> pids = new ArrayList<String>();
pids.add("550457925");
pids.add("536849918");
pids.add("505580543");
YouMayAlsoLikeResult youMayAlsoLikeResult = aeClient.youMayAlsoLike(pids, 12);
assertNotNull(youMayAlsoLikeResult);
assertNotNull(youMayAlsoLikeResult.productlist.list2015);
assertTrue(youMayAlsoLikeResult.productlist.list2015.size()==12);
} catch (AkException e) {
DefaultExceptionHandler.handle(e);
}
}
@AkTest("[收货地址]列出、添加、编辑、删除(需该用户已经存在的地址不超过4个)")
public void testMailingAddressListMailingAddresses() {
try {
aeClient.memberLogin(TestData.emailUser, TestData.password);
MailingAddressResult result = aeClient.mailingAddressListMailingAddresses();
assertNotNull(result.addressList);
result = aeClient.mailingAddressAddMailingAddress(
"3945 Freedom", "",
"US", "California", "Santa Clara",
"", "", "", "95054", "408", "1",
"43231551", "Pancras Chow", "95054"
);
assertNotNull(result);
int newId = result.newMailingAddressId;
result = aeClient.mailingAddressEditMailingAddress(
newId, "3945 Freedom modified.", "",
"US", "California", "Santa Clara",
"", "", "", "95054", "408", "1",
"43231551", "Pancras Chow", "95054"
);
assertNotNull(result);
result = aeClient.mailingAddressDeleteMailingAddress(newId);
assertNotNull(result);
} catch (AkException e) {
DefaultExceptionHandler.handle(e);
}
}
@AkTest("[其他]得到Promotion具体的商品列表")
public void testBasicPromotionCategoryProductAjax() {
try {
ArrayList<String> catIds = new ArrayList<String>();
ArrayList<String> nums = new ArrayList<String>();
catIds.add("4421");
catIds.add("4431");
catIds.add("4441");
catIds.add("4451");
catIds.add("4461");
catIds.add("4471");
nums.add("50");
nums.add("50");
nums.add("50");
nums.add("50");
nums.add("50");
nums.add("50");
PromotionCategoryProduct products =
aeClient.promotionCategoryProductAjax("100007809", catIds, nums);
assertNotNull("[失败]products is null", products);
assertNotNull("[失败]products' productlist is null", products.productlist);
} catch (AkException e) {
DefaultExceptionHandler.handle(e);
}
}