Tag Archives: Loading

新浪微博Android客户端实战 – Timeline

很久不更新了,博主快成太监了,适逢放假,继续写。

前段时间,陈华的唱吧火得一塌糊涂,也一直在用,最近才发现他们用了新浪微博客户端 sso 方式登录,原来新浪更新了SDK,代码托管从 code.google.com 移到 github。之前民间版本有Yusuke Yamamoto 写的 weibo4android,一些客户端版本也选择了在这基础上开发。都说,选择比努力更重要,我们还是选择官方的版本,因为他们专职做这些事情,能提供持续的更新的。

SDK 的使用,查看官网的pdf 文档,解释很清楚,依葫芦画瓢,我们拿到accessToken后,就好办事了。新版的API,全部使用interface,用C/C++的话来说,就是回调,相比上一个版本,使用AsyncTask显得很蹩脚。timeline 的实现,使用ListView控件,可以考虑使用下拉自动刷新组建Android-PullToRefresh android pull to refresh 基本满足要求,但需要滚动到底部也能显示“正在加载”,不怕做不到,就怕想不到,还真有人把这个给做好了, android-pulltorefresh-and-loadmore。这个组建运行在 ICS 滚动有问题,需要更新同步到 android-pulltorefresh 的 PullToRefreshListView.java。

ListView有图片需要加载,缓存,解决性能问题等,还是很麻烦的,可以使用 LazyListcwac-thumbnail等组建。为了尽快的看到结果,这里暂时使用 Andrid Query 的 image函数,使用方便。隆重介绍 Android Query,web工程师应该都知道jQuery,那么AQuery不言而喻了,写很少的代码,完成更多的功能。效果图:
weibo timeline

关于json 解析

一般有三种,Android内嵌的解析器,jackson,gson。从性能与文件大小考虑,这里使用gson。

关于分页

分页时传入page参数即可?非也!很容易理解,假设第一次取20条,第二次(page=2)再取20条,而这个过程,有插入的数据怎么办?这样是不准确的,应该是使用sinceId, maxId 这两个参数。请求大于sinceId是最新纪录,低于maxId 的是下一页(加载更多)。

内容格式化

包括:友好的时间展示,表情转义,话题(##之间),@,url等。一般使用正则表达式。

本篇代码: https://github.com/lytsing/weibo/blob/master/weibo/src/org/lytsing/android/weibo/ui/TimelineActivity.java

ListView 分段显示

Android market里软件列表,每页显示10条记录,没有显示上一页,下一页的按钮,依靠手滑动动态加载数据,当向下滚动时,最下边显示 Loading… 。数据加载结束,Loading底栏消失。

关于ListView的分段显示,有现成的库可用,比如 cwac-endless, 这个库不好之处,就是底部Loading的View无法定制。还有一个在google code上的androidpageablelistview 这个可以实现基本的分页,有手动操作显示上一页,下一页的按钮。

实现思路如下:
自定义 footer view, list_footer.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    >
    <LinearLayout
        android:gravity="center_vertical|center_horizontal"
        android:orientation="horizontal"
        android:id="@+id/loading_more"
        android:visibility="gone"
        android:layout_width="fill_parent"
        android:layout_height="?android:attr/listPreferredItemHeight"
        >
        <ProgressBar
            android:id="@+android:id/progress_large"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:indeterminate="true"
            style="?android:attr/progressBarStyleSmall"
            >
        </ProgressBar>
        <TextView
            android:id="@+id/loading_msg"
            android:paddingLeft="6.0dip"
            android:paddingTop="2.0dip"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="1.0dip"
            android:text="@string/loading"
            >
        </TextView>
    </LinearLayout>
</LinearLayout>

用到 ListView addFooterView/removeView 这两个函数。

伪代码 (Pseudocode):

private ListView mListView;
private View mFooterView;
private OnScrollListener mOnListViewScrollListener;

mListView.addFooterView(mFooterView);

mListView.removeView(mFooterView);

mListView.setOnScrollListener(mOnListViewScrollListener);

mOnListViewScrollListener = new OnScrollListener() {

    public void onScroll(AbsListView view, int firstCell, int cellCount,
            int itemCount) {
        if (firstCell != mFirstCell) {
            // balabala
        }
    }

    public void onScrollStateChanged(AbsListView view, int scrollState) {
        // Do nothing
    }
}; 

在onScroll里要处理检查是否还有新的记录,如果有,调用addFooterView,添加记录到adapter, adapter调用 notifyDataSetChanged 更新数据;如果没有记录了, 把自定义的mFooterView去掉。这里没有重写onScrollStateChanged函数,那么在onScroll就需要一个外部变量mFirstCell记录滑动位置。

再看看QQ空间体验版 for Android 是如何实现的,不用多说,show me the code:

public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    shouldRefresh = false;

    if (firstVisibleItem + visibleItemCount == totalItemCount) {
        if (bHaveMore()) {
            if (list.getFooterViewsCount() == 0) {
                addRefreshfoot();
            } else {
                shouldRefresh = true;
            }
        }
    }
}

public void onScrollStateChanged(AbsListView view, int scrollState) {
    if (shouldRefresh && scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
        if (isRefreshing == false) {
            isRefreshing = true;
            getMoreList();
        }
    }
} 

自定义ProgressBar Style

在前面写过一篇 《 Android Market的 Loading效果》,有贴出ProgressBar的图片,圆形的环是橙黄色的,从Android 2.0后,UI发生了变化,见下图。

拿QQ空间来做例子吧:

非常的简单,新建一个styles.xml文件,

<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <style name="qZoneListProgressStyle" parent="@android:style/Widget.ProgressBar.Small">
        <item name="android:indeterminateDrawable">@anim/loading</item>
    </style>
</resources>

写了个简单的示范例子,代码:qzoneLoading.tar 或在github上也可以下载 https://github.com/lytsing/qzoneLoading,效果图为:

留意了一下UC浏览器的进度loading:

开始也以为是修改styles即可,反编译一下,才知道是在新建一个DrawWaitingPage.java类绘画. 大概是用到Paint,Canvas,Timer之类的,自定义一个View。

Android Market的 Loading效果

在Android中,要实现Loading效果,一般情况下都使用ProgressDialog控件。ApiDemos/src/com/example/android/apis/view/ProgressBar3.java 提供两个demo:
Progressbar show Indeterminate
progressbar show Indeterminate No Title
仔细看了Android Market,发现却是不一样的,请看截图:
那到底如何实现呢?首先,我们创建一个布局文件,
res/layout/fullscreen_loading_indicator.xml, 其内容如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:gravity="center_vertical|center_horizontal"
    android:orientation="horizontal"
    android:id="@+id/fullscreen_loading_indicator"
    android:visibility="gone"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <ProgressBar
        android:layout_gravity="center_vertical"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        style="?android:attr/progressBarStyleSmall"
        >
    </ProgressBar>
    <TextView
        android:id="@+id/current_action"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5.0dip"
        android:text="@string/loading"
        >
    </TextView>
</LinearLayout>
然后在main.xml 把它include 进来

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <LinearLayout
        android:orientation="vertical"
        android:id="@+id/main_info"
        android:visibility="gone"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        >
    </LinearLayout>
    <include
        android:visibility="visible"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        layout="@layout/fullscreen_loading_indicator"
        >
    </include>
</FrameLayout>
主程序 Loading.java:
package org.lytsing.android.loading;

import android.app.Activity;
import android.os.Bundle;
import android.widget.LinearLayout;

public class Loading extends Activity {

    private LinearLayout mLoadingLayout;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // TODO: dismiss the loading, use this snippet code.
        //mLoadingLayout = (LinearLayout)findViewById(R.id.fullscreen_loading_indicator);
        //mLoadingLayout.setVisibility(View.GONE);
    }
}
运行的效果为: