天天看点

面试知识储备:新浪微博Android客户端的实现

项目介绍及完成的logo介绍

  1. 项目介绍

    启动界面(3秒进入主界面)——用户第一次登陆的界面 + “授权”按钮——授权界面——微博首页

  2. 技术要点

    1、Aouth认证

    2、获取微博列表

    3、发表博客

    4、评论博客

    5、转发博客

    其他技术要点:

    布局 + UI + Handler + Service + 线程 + SQLite + SharedPreferences + 网络通信 + 文件上传下载 + 拍照 + 项目打包发布 + 项目架构

  3. 完成的Logo界面

    1、实现在欢迎界面停止三秒后进入主界面

    小知识1:Android Animation动画效果之一:alpha 渐变透明度动画效果

//formAlpha:动画起始透明度
//toAlpha:动画结束时透明度,值均为0.0(完全透明)——1.0(完全不透明)
//duration:动画的持续时间
//为动画设置相应的监听
        AlphaAnimation animation = new AlphaAnimation(, );
        animation.setDuration();
        imageLogo.setAnimation(animation);
        animation.setAnimationListener(new AnimationListener();//动画结束的时候跳转到登录界面
           

2、Activity的标题栏和状态的隐藏(即Android界面的全屏显示)

方式一:在setContentView上添加如下代码:

//取消标题栏
  this.requestWindowFeature(Window.FEATURE_NO_TITLE);
//取消状态栏     this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
           

系统主框架的搭建

(采用的MVC模式)

1. Handler介绍

2. Service介绍

3. 系统框架搭建

系统结构

1. UI层:存放各个Activity,与用户交互

2. 业务逻辑层(核心层):程序的核心调度模块,例如:逻辑层接收UI层发送过来的任务,调用相应微博接口获取网络数据来处理任务,最后将处理结构返回给UI,并且刷新UI

核心类:

1、Task类,实际是一个bean类,属性为Taskid(标识用户发送过来的任务),taskParams(HashMap类型,标识任务携带的数据)

2、IWeiboActivity:所有的界面都会实现这个接口,含两个方法:init()和refresh(Object…prarm)

3、MainService:既是一个Service又是一个Thread,在后台启动线程,用户在UI上的操作对应一个特定id的任务,将任务添加到Mainservice的任务队列中,然后启动线程死循环处理任务队列中的任务。

3. 模型层:数据库(UserInfo + Task)

整个逻辑层的执行流程

1、程序运行以后,在后台启动一个服务,并开启一个线程不断监听UI传过来的任务

2、UI将用户的操作,也就是任务传给logic层的MainService,

3、MainService将用户传过来的任务保存到一个队列中,并开启线程处理任务队列里的任务

4、任务处理完毕之后,将任务处理结构返回给UI,通过Handler及时修改界面

面试知识储备:新浪微博Android客户端的实现

总结:新浪微博客户端的的架构也是采用的MVC结构

  1. 视图层呢包含了一个个的Activity,每个Activity都会实现两个抽象方法,一个方法是刚进入到这个Activity时候的界面初始函数、另一个执行了某个操作之后界面的更新函数,譬如说更新原来activity的内容啊,又或者是跳转到一个新的activity啊。
  2. 控制层,也就是逻辑层的,它是一个运行在后台的Service类,也是一个线程类。它里面维护的两个比较重要的存储结构,一个是任务队列,另一个是要更新的Activity的ArrayList,也就是说用户在Activity上的操作都会形成特定id的任务,添加到任务队列里面。然后MainService会启动线程根据任务的id执行相应的操作,最后根据处理结构更新Activity。这里面又会涉及到主线程和子线程之间的通信,因此也会有到Handler。
  3. 模型成的话就涉及到一些bean对象啊,譬如说之前提到的任务啊、用户信息啊这些bean对象。

用户的OAuth授权认证

用户登录流程

1、欢迎界面—登录Activity—查询数据库(SQLite),用户是否授权

2、如果SQLite有该用户:保存当前登录用户(自动登录)

3、SQLite没有该用户(未授权):进入验证界面——验证通过后将(token、userId、tokenSecret保存到数据库中)—返回登录界面

小知识2:让按钮发生变化:可以自定义xml布局:

//分别表示三种不同状态下按钮的变化
<?xml version="1.0" encoding="utf-8"?>
<selector
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_focused="true" android:state_enabled="true" android:state_pressed="false" android:drawable="@drawable/add_account_n" />
    <item android:state_enabled="true" android:state_pressed="true" android:drawable="@drawable/add_account_p" />
    <item android:state_enabled="true" android:state_checked="true" android:drawable="@drawable/add_account_p" />
    <item android:drawable="@drawable/add_account_n" />
</selector>
           

小知识3:显示自定义的dialog

//加载自定义的布局文件
View dialogView = View.inflate(this, R.layout.activity_auth_begin, null);
        dialog = new Dialog(this);
        dialog.setContentView(dialogView);
        dialog.show();
           

新浪微博授权流程

1、点击”开始”——进入新浪微博的授权页面(WebView加载)——输入用户名和密码授权——获取PIN值——获取AccessToken——保存到数据库中

将用户授权信息保存到数据库中

创建数据库表

  1. Android中内嵌了一个很小的、基于文本的关系型轻量级数据库SQLite。SQLite数据库操作步骤为:

    创建SQLiteOpenHelper对象,根据SQLiteOpenHelper创建或打开数据库,SQLite有两种不同类型的数据库:readerDatabase、WriterDatabase.

  2. 表为:user_info,即用户的登录及授权信息:AccessToken + 用户id + 用户名 + 用户头像(bean中定义成drawable对象) + 默认账号
  3. 将用户信息插入到数据库中:

    1、创建专门访问数据库的包service

    2、创建专门对用户基本信息操作的类:UserInfoService

    3、插入用户:insert(table,null ContentValues),其中ContentValues为存放数据的键值对(“username”,”dengqi”)

    4、一张表应该对应于一个javabean对象,javabean对象作为信息的载体

    5、用户信息的插入是从javabean中取的数据然后插入的(用户信息使用AccessToken中获得的),insert(user)

    6、用户信息的查询是从数据库中查询并Set保存在javabean中

    7、导出数据库并用navicat打开数据库

用户登录的实现

  1. 界面效果是:点击下拉框,显示授权过的用户dialog,选择登录
  2. 查询userInfo表中所有用户的信息,先将结果变为user对象,再保存在ArrayList中。

    注意:用户头像中在数据库中存储的是byte[]数组,,而Javabean中用户头像信息是Drawable,二者之间的转换为:

ByteArrayInputStream is = new ByteArrayinputStream(byteIcon);
Drawable userIcon = Drawable.createFromStream(is,"image");
           
  1. 如果查询结果用户list为空,则进入授权界面。(在LoginActivity中init()方法中获取查询结果);list中有值的话就启动服务Mainservice,开启线程死循环监听Activity任务。
  2. 选择登录用户,点击按钮弹出选择用户对话框
//提取布局文件,一个现实用户的listView
View viewDialog = View.inflate(LoginActivity.this, R.layout.user_selected_dialog(一个ListView));
//创建Dialog对话框(自定义样式)
Dialog dialog = new Dialog(LoginActivity.this,R.style.user_selected_dialog(dialog的样式));
//为Dialog设置内容
dialog.setContentView(viewDialog );
dialog.show();
           

其中listView的使用方式为:

SimpleAdapter listItemAdapter = new SimpleAdatper(context, initData(初始数据), R.layout.list_itemslistItem的布局文件,new String[显示HashMap中的哪些key],new int[R.id.XX将HashMap中key的值现实在对应的控件上])
list.setAdapter(listItemAdapter)
           

创建一个新的任务,用户获取用户选择登录的userID和头像,步骤为:

1、new Task(TaskID, UserInfo),并将任务添加到MainService任务队列中

2、MainService获取任务后,根据weibo对象(只要初始化一次就行)获取用户头像,步骤为

①:通过AccessToken获取Weibo对象

②:weibo.setToken(userInfo.getToken,userInfo.gettokenSecret)类似于用户登录的过程

③:根据微博API获取用户信息

这里用户头像是一个url地址

User user = weibo.showUser(userInfo.getUserId())
userInfo.setUserName(user.getName())
user.getProfileImageURL();  //返回头像的url地址
           
获取url地址对应的图片
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
Drawable drawable = Drawable.createFromStream(connection,getInputStream(),"image");
           

④:将信息更全面的userInfo通过handler回传给主线程,主线程获取userInfo信息后修改界面,(refresh也采用一个标志符修改相应的界面)

(注意要把String保存到string.xml文件中,以减小内存的使用)

⑤、更新数据库信息

注意图片Drawable保存到数据库中的方式,数据库中存储图片的方式采用的是bitmap

⑥:选中后修改登录图片和登录用户(ListView.setOnItemClickListener)

⑦:点击登录按钮后,一方面跳转到主界面,另一方面,将当前登录的用户nowLogin保存到sharePreferences中,下次用户再登录的话就直接跳转到微博主界面而不用进入到登录界面

//保存
SharedPreferences sp= context.getSharedpreferences(xml文件名, 保存模
式);
Editor editor = sp.edit();
edit.putString(保存的数据名称,值);
editor.commit
           
// 取数据
SharedPreferences sp= context.getSharedpreferences(xml文件名, 保存模
式);
String userId = sp.getString(保存的数据名称,取不到时数据的默认值);
if("".equals(userId)) {
        return null;//如果sp中没数据,代表没有登录的用户
} else {
    UserInfo = new UserInfo(...)//如果sp中有数据,返回上一次登录的用户对象,直接进入主界面
}
           

⑧:登录在保存当前登录用户信息还有页面跳转之前,还要告诉Mainservice当前登录的用户是哪一个(类似于web中将当前用户保存在Session)

这样做:

1)在MainService中创建一个当前用户static UserInfo nowUser,用于接收当前登录用户

2)

Map<String, Object> taskParams = new HashMap<String, Object>();
taskParams.put("user", nowUser);
//
Task task = new Task(LOGIN, taskParams);
MainService,newTask(task);
           

3)MainService通过Task获取到当前登录用户之后,再通过当前用户的AccessToken创建当前用户对应的weibo对象(也是一个)

4) MainService处理登录任务(进度条 + 页面调转 + 不管是直接进入主界面的登录还是选中用户的登录都要给MainService发送当前登录用户的信息)

微博主界面的搭建

微博界面涉及的功能

微博ListView + 刷新微博 + 写微博 + 进度条

进度条简单介绍

  1. 会经常使用,应该单独将进度条设计成一个layout被其他文件引用
  2. 条形进度条 + 圆形进度条
  3. setMax(100)设置进度条最大值 + setProgress(0)设置当前进度条

微博ListView中Item的设置

t:相对于父控件的布局

4、AbsoluteLayout:

5、TableLayout

(在Drawable的左边设置一张图片:TextVeiw的属性DrawableLeft)

3. 获取用户好友(关注)的最新信息

调用微博API:weibo.getFriendTimeline(或者参数为分页,无page则为20条微博信息)(查看微博的API,返回一个json数据,status(表示微博信息内容))

1、调用getFriendTimeline这个API获取数据会访问网络,因此也一定要遵从实现定义好的MVC模式:在HomeActivity中定义task添加到MainService队列中 + 把自己的Activity添加到MainService中:

Task task = new Task(Task.WEIBO_FRIENDS_TIMELINE, null);
MainService.newTask(Task);
MainServie.addActivity(this);
           

2、在MainService中doTask(后台服务 + 线程)调用List[Status] status = weibo.getFriendTimeline

3、通过handle将status返回给主线程

4、MainService主线程中根据获得的HomeActivity和status对象,调用HomeActivity中的refresh方法更新HomeActivity

5、获取数据之后List[Status],将数据源与Item绑定(BaseAdapter)

显示数据:不必要显示的数据先设置成不可见,有数据的话则显示(例如:是否认证,微博内容中是否有图片:s.getThumbnal_pic() + 判断是否有转发:s.getRetweeted_status() + 显示来自哪里:Html.fromHtml(e.getSource(给出了渲染超链接的方法);))

ListView微博主页界面的美化

  1. 使用自定义的样式显示ProgressBar

    默认是:style=“?Android:attr/progressBarStyleLarge”

1、在values文件的styles.xml文件中添加集成父样式如下样式(只改变图片) + 新建一个Animation
<style name="myProgressStyle" parent="android:style/
Widget.PropgressBar.Large">
    <iten name="android:indeterminatedDrawable">@drawable/myProgressBar</item>
</style>
2、在drawable中定义rotate的动画(一个xml文件)
<animated-rotate
    android:drawable="drawable/progress"
    android:pivotX="50%"
    android:pivotX="50%"
    //转动的坐标点
/>
           
  1. 每个Item之间的分割线(设置成虚线分割线)

    实现方式是:将一张图片平铺(推荐使用xml文件的方式实现)

定义:
<bitmap
    android:src="@drawable/bg_dot"
    android:titleMode="repeat"
/>
使用:
在ListView中使用属性:
Android:divider="@drawable/line"
           
  1. Item的按下效果(不能为整个ListView设置bg,类似于button不同的状态效果)(参考Android中各种xml文件的作用)
选中Item后显示Android内置的样式:
@android:Drawable/list_selector_background
           

ListView中图片的缓存和异步加载

(流程和思想可以参考自己的那个ListView异步加载和缓存图片)

面试知识储备:新浪微博Android客户端的实现
  1. ListView缓存和异步加载图片的流程:

    通过微博API获取到的是图片的url,所以还有通过这个url从网上下载这个图片,这就涉及到ListView中图片的缓存和异步加载。

    每一个Item都会有一个Callback对象,当获取到图片之后再通过这个Callback(根据url和view创建的CallBack对象,重写refresh更新UI的方法)对象更新Item中的图片

    1、首先根据图片的url查找缓存中是否有该图片,缓存的话其实就是一个key为图片url,值为url对应的BitMap的HashMap

    2、如果缓存中没有的话就先将,则将图片的url和对应的callback对象添加到一个HashMap[url,ArrayList[CallBack]]

    3、将Item中获取的图片URL存储到一个队列中,然后开启线程,死循环获取队列中的url

    4、首先根据url查看文件中是否有该Bitmap文件,有的话直接获取并存入缓存;没有的话就从网上下载该图片,存入文件中。

    5、将获取到的url对应的bitmap通过Handler返回给主线程,最后通过url地址获取对应的CallBack对象更新Item的UI

  2. 涉及到的类

    1、LazyImageLoader { //异步加载图片

    get();//调用ImageManager方法

    Handler//call ImageView;

    }

2、ImageManager:管理缓存

getFromCache() {
    getFormMap();
    getFromFile();
}; 
DownLoad();
putToCache(){
    putToMap();
    putToFile();
    }
}
           

3、ImageCallBack

Interface ImageCallBack {
    callBack();
}
           

4、类CallManager

  1. 实现

    1、新建接口ImageLoaderCallback

public interface ImageLoaderCallback {
    void refresh(String url, Bitmap bitmap);
    //通过从网络上下载的bitmap更新ImageView
}
           

2、get方法的实现

①新建类LazyImageLoader,在该类中实现get方法

private ImageManager imgManager = new ();
//存放url的队列(队列为空时会阻塞)
private BlockingQueue[String] urlQueue = new ArrayBlockingQueue[String][];
private DownloadImageThread downImgThread = new ();

public Bitmap get(String url, ImageLoaderCallback callback) {
    //先从缓存中获取;转去编写ImageManager类中的方法1)
    //2)如果内存缓存有数据,从内存缓存中读取数据
    Bitmap bitmap = null;
    if(imgManager.contains(url)) {
    //转去实现ImageManager中内存缓存中获取数据3)
        bitmap = imgManger.getFormCache(url);
        return bitmap;
    }
    //缓存中没有,则开启线程从网络中加载图片
    else {

        //开启线程下载图片,但是下载要使用url,应采用队列 + 线程死循环的的方式不断不断下载队列url中的图片 
        startDownLoadThread();
    }

    //开启线程,这样用状态来开启线程的目的是为了
    private void startDownLoadThread() {
            //开启线程之前,先将url放到队列中
            urlQueue.put(url);
            State state = downloadImgThread.getState();
            if(state == State.NEW) {
                downloadImgThread.start();
            }
            //线程已经执行结束,才创建新的线程并且启动
            else if(state == State.TERMINATED){
                downloadImgThread= new ();
                downloadImgThread.start();
            }   
}
    private class DownLoadImageThread extends Thread {
        piravte boolean isRun = true;

        public void run() {
        //从网络中下载图片      
        //死循环从url队列中获取一个url
            while(isRun){
                String url = urlQueue.poll;
                if(null !=- url) {
                //从网络中下载该URL图片(还要将图片保存到文件中)             
                }
            }
        }
    }
}
           

②编写ImageManager

public class ImageManager {
// 1)
//直接用bitmap系统不能很好地回收内存资源,因此改用SoftReference
    Map[String, SoftReference[Bitmap]] imgCache;
    //还要一个Context对象获取相应的IO进行读写文件
    private Context context;
    public ImageManager(Context context) {
        this.context = context;
        imgCache = new HashMap();
    }
//判断内存缓存中是否含有该URL对应的bitmap
public boolean contain(String url) {
    return imgCache.containKey(url);
    }
//转去执行LazyLoaderImage2)
}
//从缓存中获取Bitmap(没存缓存 + 文件缓存)
public BitMap getFromCache(String url) {
    Bitmap bitmap = null;
    bitmap = this.getFromMapCache(url);
    if(null == bitmap) {
        bitmap  = getFromFile(url)  
    }
    return bitmap;
}

//从内存缓存中获取数据③
public Bitmap getFromMapCache(String url) {
    Bitmap bitmap = null;
    SoftReference[Bitmap] ref = null;
    synchronized(this) {
    ref = imgCache.get(url)
    }
    if(ref != null) {
        bitmap = ref.get();
        return bitmap
    }
}
//内存缓存中没有则从文件缓存中查找
public Bitmap getFromFile(String url) {
    // Android中提供了如下方法对系统内部的资源进行访问,封装了IO流
    //文件名不能包含有分隔符,所以应该用MD5加密一下再使用
    String fileName = this.getMD5(url);
    FileInputStream is = context.openFileInputStream(文件名);
    return BitmapFactory.decodeStream(is);
    //try catch finally 关闭流
}
           

CallBackManager类

  1. 涉及到的方法:
private ConcurrentHashMap
public void callback(String url, Bitmap bitmap) {

}
           

在TextView中高亮显示文本和表情

  1. 先要了解的类与接口:

    Spannable接口

    实现Spannable接口的两个类:SpannableString + SpannableStringBuilder

    重要方法:setSpan

    ImageSpan(图片) + ForegroundColorSpan(高亮) + URLSpan(URL)

  2. 例子:字符串高亮
SpannableString spannableString = new SpannableString(str);
ForeGroundColorSpan span = new ForeGroundColorSpan(Color.blue);
spannableString.setSpan(span, , ,Spannable.SPAN_EXECLUSIVE_EXCLUSIVE);
textView.setText(spannableString);
           
SpannableString spannableString = new SpannableString(str);
Drawable drawable = getResources().getDrwaable(R.drawable.icon);
drawable.setBound(, , , ,);//不然会报错
ImageSpan imgSpan = new ImageSpan(drawable);
spannableString.setSpan(imgSpan, , ,Spannable.SPAN_EXECLUSIVE_EXCLUSIVE)
textView.setText(spannableString);//就会在该TextView的指定位置显示图片而不是文字了
           
  1. 微博内容中需要高亮的内容有:#话题#、@人名 、url、[表情]、
  2. 通过正则表达式获取微博特定字符的在整个String中的位置
//找出模式的起止位置保存到List中
public void getStartAndEnd(Pattern pattern) {
    List<HashMap<String,String>> list = new();
    Matcher matcher = pattern.matcher(str);
    while(matcher.find()) {
        HashMap<String,String> map = new();
        map.put("start", matcher.start() + "");
        map.put("end", matcher.start() + "");
        list.add(map);
    }
    return list;

}

//高亮的方法
private static final String TOPIC = #.+?#;//表示话题的正则表达式
参数为Pattern.compile(TOPIC)
public void highlight(Pattern pattern)) {
    List<HashMap<String,String>> list = getStartAndEnd(pattern);
    for(Hash){
        ForeGroundColorSpan span = new ForeGroundColorSpan(Color.blue);
spannableString.setSpan(span, map.get("start"), map.get("end"),Spannable.SPAN_EXECLUSIVE_EXCLUSIVE);
    }

}
           

(上一部分内容CallBack没怎么弄明白,先防范)

ListView中高亮显示文本和表情

在TextView上直接显示的微博内容是没有样式的,实际上TextView中部分内容,譬如说标题、人名的高亮显示,字体的颜色还有URL格式、表情这些内容都是有样式的,因此采用SpannableString结合正则表达式的方式来设置TextView中文本的样式。

根据正则表达式找出符合正则表达式的SpannableString中字符串的起始和结束位置的字符串,存入到ListView中,之后遍历ListView中所有符合正则表达式的字符串设置相应的样式。

要高亮显示的部分:话题 + URL + @人名 + 表情即为TextView中的文字设置属性:对于TextView中各个部分的文本来设置字体,大小,颜色,样式,以及超级链接等属性。

  1. 设计的接口与类:

    Spannable(接口) + SpannableString(SpannableStringBuilder) + setSpan方法 + ImageSpan(为文字设置图片) + ForegroundColorSpan(为文字设置高亮) + URLSpan(URL)

  2. 例子
SpannableString spannableString = new(str);
//文字高亮
ForegroundColorSpan span = new(高亮的颜色);
spannableString.setSpan(span,startPosition,endPosition,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// 为文字设置图片
Drawable drawable= getResources().setBound();
ImageSpan imgSpan = new ImageSpan(drawable);
spannableString.setSpan(span,startPosition,endPosition,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
texView.setText(spannableString);
           
  1. 通过正则表达式获取特定字符开始和结束的位置
public vod getStartAndEnd(Pattern pattern) {
    List<HashMap<String, String>> list = new ArrayList();
    Matcher matcher = pattern.matcher(str);
    while(matcher.find()) {
        new HashMap();
        map.put("start", matcher.start() + "");
        map.put("end", matcher.end() + "");
        list.add(map);
    }
    return list;
}

//
//首先定义正则表达式:话题高亮:#话题# + @人名 + URL三者的正则表达式
public static final String TOPIC = "#.+?#"
//传入的值为Pattern.compile(TOPIC);
public void hightLight(Pattern pattern){
    List list = this.getStartAndEnd(pattern);
    for(HashMap map : list) {
            ForegroundColorSpan span = new(高亮的颜色);
    spannableString.setSpan(span,map.get("start"),map.get("end"),Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    }
    textView.setText(spannableString);

}
           

微博表情的解析

微博API——写入接口——emotions获取官方表情

表情json数据中两个属性:url:表情动态图(下载到本地Drawable) + value:[呵呵],通过spanableString结合正则表达式将表情字符串

替换成相应的gif图

  1. 将一个个[表情]添加到list[HashMap[string,String]]中

    map.put(“pharse”,matcher.group);

  2. 下载表情到drawable中

    1、新建一个任务、

    2、添加到MainService的任务队列中

    3、在线程中条用微博API接口下载表情:

    List[Emotion] emotions = weibo.getEmotions(“face”,”cnname”)

    4、通过handler返回给MainService主线程,获得emotion中表情的别名和表情图片url;

    5、通过url下载表情图片(将表情图片的url先放到队列中 + 开启线程死循环下载表情图片到指定的文件中(获取url对象,获取url对象 + 根据url获取代表连接的对象 + 通过http连接的对象获取流对象))

  3. 解析保存到HashMap[表情字符串别名,对应Drawable的id]
//表情别名存放在一个字符串数组中
<string-array name="default_emotions">
    <item>[爱你]</item>
    <item>[花心]</item>
    <item>[抓狂]</item>
</string-array>

//对表情进行解析
public class EmotionsParse {
    private static HashMap<String,Integer> emotionMap;
    private static String[] phrases;
    //记得要与表情别名数组一一对应
    private int[] emotions_id = {
        R.drawable.love,
        R.drawable.hx,
        R.drawable.crazy
    };
    public EmotionsParse(Contextcontext) {
        phrases = context.getResources().getStringArray(R.array.default_emotions)
        //将表情别名和对应的图片存放在HashMap中
    }

}
           
  1. 将表情字符串替换成相应的gif图
public static final String PHRASE=??"
//传入的值为Pattern.compile(TOPIC);
public void phrase(Pattern pattern){
    List list = this.getStartAndEnd(pattern);
    for(HashMap map : list) {
        Drawable drawable= getResources().setBound();
        ImageSpan imgSpan = new ImageSpan(drawable);                                         
    spannableString.setSpan(span,startPosition,endPosition,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    }
    textView.setText(spannableString);
}
           

微博解析整个流程(与下载图片类似,看来这个要熟练掌握)

SpannableStringBulder + callback + 启动线程解析 + 把微博内容放入队列 + 解析微博 + handler更新 + 在handleMessage中调用CallBack方法即可 + 更新ListView的Item

图片的放大与缩小 + 图片拖动和多点触摸

(设置图片匹配ImageView的属性scaleType=“fitXY”)

  1. 微博中点击图片放大

    1、为ImageView设置点击监听,并为监听类传递两个参数:originalPicUrl bmiddlePicUrl原始图片地址 + 中等图片地址

    点击后弹出一个dialog,

    2、通过bmiddlePicUrl从网络中异步加载图片,获取到bitMap后通过CallBack更新Dialog的ImageView

  2. 图片放大缩小的实现思路

    (Android提供了Matrix类来对图片的进行处理,包括图片的:平移 + 旋转 + 缩放 + 倾斜)

    点击按钮实现缩放:首先获得缩放后图片的大小;根据缩放后图片的大小新建一个Matrix对象,通过Matrix对象的大小设置图片的缩放;

    实现放大后图片的滑动:为ImageView设置屏幕触摸事件监听;获取屏幕点击事件,记录初始点击的坐标;然后获取屏幕滑动事件,记录滑动后的坐标;根据滑动后的左边修改Matrix对象;最后通过Matrix对象设置图片的滑动。

    实现多点触摸缩放图片:为ImageView设置屏幕触摸事件监听;获取屏幕双点击事件,记录两个初始点击的坐标;然后获取屏幕滑动事件,记录滑动后的坐标;根据滑动后两点的左边距离/初始距离计算图片的缩放大小;最后通过Matrix对象设置图片的缩放。

    1、获取图片

    2、重新生成一张放大缩小后的图片:获取屏幕双点击事件,记录初始点击的坐标;然后获取屏幕双滑动事件,记录双滑动后的坐标;通过双滑动后坐标的距离/初始距离计算缩放的倍数;通过Matrix对象的大小设置图片的缩放。

matrix.post(scaleWidth, scaleHeight)
Bitmap reSizeBmp=Bitmap.createBitmap(bitmap, , , bmpWidth, bmtHeight, matrix, true);
           

3、重新生成一个新的ImageView

4、移除旧的ImageView,添加新的ImageViwew

但是这种图片的放大缩小方式会导致内存溢出,实际上不用这么做,Android提供了通过Matrix矩阵类来实现图片的缩小和放大

ListView的分页显示

分页总结:每一条微博记录中都有一个Max_id用来标识微博发表的次序;第一次加载微博记录的时候,会加载指定数量的最新微博,保存到ArrayList中(ArrayList[Status]),通过这个ArrayList中的数据更新ListView;这样也就获得了最后一条微博记录的Max_id,点击“加载更多”布局的时候会向MainService发送一个任务,并且传递一个参数Max_id,MainService开启线程从队列中取出这个任务,通过Max_id调用微博API获取小于Max_id的指定书目的微博记录,添加到一开始的ArrayList[Status]中,并且notify(通知)Adapter数据源发生了改变;最后显示新获取的微博数据,不过在显示ArrayList[Status]数据的时候要显示特定位置上的数据setSelection。

1. 在ListView的底部添加一个“显示更多”,显示更多 + 正在加载和进度条(隐藏)

loadMoreView = View.inflate(this, R.layout.more_layout, null);
weiboListView.addFooterView(loadMoreView, null, true);//要在setAdapter之前调用,在ListView的底部添加一个布局
           
  1. 获取微博信息Status的方法:

会传入一个page对象

page对象中有一个max_id属性,指定该参数后,返回小于或等于max_id的微博(Page对象的属性有since_id(返回比该值大的微博,即比该id时间晚的微博)、max_id(返回比该值小或等的微博)、count(每一页显示的记录)、page(返回哪一页))

3. 将max_id作为参数添加到MainService任务队列中(原来获取微博信息的任务),在MainService这样处理任务:

Paging page = new Paging();
if(maxId <= )  //初始的时候maxId为0,是特殊情况
    page.setPage();
else
    page.setMaxId(maxId);
page.setCount();
           
  1. 点击加载更多Layout时候,弹出进度条,显示正在加载,隐藏加载更多,随后执行加载更多方法加载更多方法。
  2. 第一次加载指定数量的微博Status,然后通过refresh方法更新ListView,此时更新maxId值,(获取最后一条微博status)

刷新微博

点击刷新按钮实现微博刷新

1. 刷新按钮的动画显示

2. 刷新功能的实现:刷新的话跟加载更多的实现差不多,不过刷新的话都要创建新的adapter(第一次加载数据的时候也要创建,但之后的加载更多就不用),获取到;

ListView下拉刷新自定义组件的实现

涉及到的知识点:自定义控件如何做 + ListView的下拉刷新(界面布局 + 下拉动画 + 刷新的实现)

  1. 自定义ListView控件的实现:
//这个构造方法在findViewById的时候会被调用
public class PullToRefreshListView extends ListView {
    public PullToRefreshListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
}
           
<com.dengqi.pullrefreshlistview.PullToRefreshListView
        android:id="@+id/pull_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
   </com.dengqi.pullrefreshlistview.PullToRefreshListView>
           

接着在MainActivity中更普通ListView使用即可

2. 界面布局的实现(要求隐藏刷新)

public void setAdapter(ListAdapter adapter) {
        // TODO Auto-generated method stub
        super.setAdapter(adapter);
        setSelection();//从ListView中第一个Item数据开始显示
    }
           
  1. 下拉刷新效果的实现

    点击——拖拽——一直会计算计算拖拽距离——当拖拽距离>刷新布局高度的时候——显示正在刷新效果——松开后刷新ListView + 还原刷新布局

    1、定义刷新的几种状态(初始状态 + 拖拽状态 + 释放状态 + 正在刷新状态(正在刷新状态的时候是不允许有其他状态的))

    2、重写ListView的触摸监听事件,根据相应的动作执行相应的操作

    ①:按下动作:获取按下时Y的坐标

    ②:拖拽动作:根据拖拽的距离不断去更改头部刷新布局的TopPadding属性,让布局有下拉的效果 + 通过MeasureSpec测量空间的高度

//拖拽过程中执行的操作
        //获取拖拽过程中点的个数
        int pointCount = event.getHistorySize();
        for(int i = ; i < pointCount; i ++) {
            //获取历史积累的高度
            int hisY = (int)event.getHistoricalY(i);
            int topPadding = (int)((hisY - mLastMotionY) - headLayoutHeight/);
            Log.i(TAG, "topPadding = " + topPadding + "; headLayoutHeight" + headLayoutHeight);
            headLayout.setPadding(headLayout.getPaddingLeft(), topPadding, headLayout.getPaddingRight(), headLayout.getPaddingBottom());
        }
           

③:释放动作:加载最新的微博并且是 + 还原布局

系统退出

  1. 点击返回按钮(keyCode== KeyEvent.KEYCODE_BACK),弹出是否确认退出对话框
  2. 点击确认退出,则退出系统,遍历之前所有的Activity,finish + MainService,stop
for (Activity activity : appActivities) {
        if(!activity.isFinishing())
            activity.finish();
    }   
    // 结束 Service
    Intent service = new Intent("com.droidstouch.iweibo.logic.MainService");
        context.stopService(service);
           

微博详情

点击某一条微博,跳转(使用startActivityForResult跳转)到微博详情界面,根据该条微博status的id调用微博API接口(Status showStatus(long id) ),返回微博详情Status,并更新UI(其中图片还是要用到异步加载和缓存)

发表微博

  1. 发表微博界面

    顶部:返回按钮 + “发表微博” + 发表按钮

    中部:EditText编辑框(140字的限制)

    底部:定位 + 拍照 + @ + 表情

  2. 发表微博接口介绍

    1、最基本的方法:Status updateStatus(String status),即只发表文字的发表微博API;

    2、重载的话还可以发表图片(二进制形式 + 图片的格式) + 上传经纬度

  3. 微博字数限制:

    可以为EditText设置监听、监听EditText的字数变化

et_weibo.addTextChangedListener(new TextWatcher() {
    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
            }
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }
   @Override
    public void afterTextChanged(Editable s){
        txtLimit.setText(MAX_LIMIT - s.length() +"");
    }
}); 
           

Android项目的发布、签名、打包

(命令行打包 + ant打包 + ADT打包)

ADT打包方式:project——Android Tools——Export Signed Application Package——create new keyStore(创建新的私钥文件:私钥名称 + 密码)——生成APK文件