android开发新浪微博客户端 完整攻略 [新手必读][转]2011-04-06 10:49:19
http://www.eoeandroid.com/forum-viewthread-tid-67298-fromuid-106432.html

开始接触学习android已经有3个礼拜了,一直都是对着android的sdk文档写Tutorials从Hello World到Notepad Tutorial算是初步入门了吧,刚好最近对微博感兴趣就打算开发个android版本的新浪微博客户端作为练手项目,并且以随笔的方式详细的记录开发的全过程。本人对java语言以及eclipse Ide都是初次应用基本上属于边学边用,做移动设备上的东西也是第一次,总的来说属于无基础、无经验、无天赋的纯三无人员,还请广大同学们多多给予指点。

  开发第一件事情,那就是开发工具以及环境,我的配置是Eclipse Helios (3.6.1) + Adroid2.2,具体的环境搭建我就不罗嗦了,google一下一大堆,光博客园里都能搜到很多篇了。

  开发第二件事情,既然是开发新浪的微博客户端,那就先去新浪申请微博账号然后登陆后到新浪的开放平台,新浪的开放平台提供的新浪微博对外的api接口,在我的应用中创建一个新的应用获取App Key和App Secret,这2个值后面会有用到先记录下来。在新浪的开放平台中提供了开发文档、SDK、接口测试工具等,本人决定直接通过新浪的Rest Api进行开发并不打算使用新浪提供的SDK,据说新浪提供的java版的SDK并不能直接用来进行android的开发需要进行一定的修改才能使用,只是听说我没有试过不一定准确。

  最后在说一下,我准备分为UI和功能两部分分别进行说明讲解,据我自己的情况大部分的时间都花在的UI的设计和实现上了,编码倒反而工作量小多了,所以特别把UI部分分出来讲。

  最后还要在说一下,很抱歉上面内容基本上属于废话没有什么实质内容了但是既然是第一篇还是得象征性的交代一下,从下篇开始讲具体的内容。

本软件设定用户第一个接触到的功能就是页面载入等待功能,这个功能对使用者来说就是一个持续1、2秒钟的等待页面,在用户等待的同时程序做一些必要的检查以及数据准备工作,载入页面分为UI篇和功能篇,从表及里首先是UI的实现,一个软件除功能之外还得有一个光鲜的外表也是非常重要的,尽管本人设计水平一般但是还是亲自操刀用ps先做了一下设计效果图如下:

6天前 上传 下载附件 (131.9 KB)



  一、接下来的任务就是在android中实现这样的效果显示,从这个效果的设计分别把图片分成背景、版本号部分、软件名称和图标、作者名称和blog四个部分,按照这样的思路把分别生成4张png的图片,背景部分考虑实现横屏和竖屏切换额外添加一张横屏背景图,然后新建android工程,我这里的名称为MySinaWeibo,android版本勾选2.2,并且创建名为MainActivity的Activity作为整个软件的起始页面,然后把上面的这些图片保存到项目的res/drawable-mdpi文件夹下,关于res目录下的drawable-mdpi、drawable-ldpi,、drawable-hdpi三个文件夹的区别,mdpi 里面主要放中等分辨率的图片,如HVGA (320x480)。ldpi里面主要放低分辨率的图片,如QVGA (240x320)。hdpi里面主要放高分辨率的图片,如WVGA (480x800),FWVGA (480x854)。android系统会根据机器的分辨率来分别到这几个文件夹里面去找对应的图片,在开发程序时为了兼容不同平台不同屏幕,建议各自文件夹根据需求均存放不同版本图片,我这里就不进行这么多的考虑了。

  二、完成图片资源的准备后接下就是layout文件的编写, 在res/layout文件夹下新建main.xml文件,这个layout采用LinearLayout控件作为顶层控件,然后用ImageView控件分别实现版本号图片顶部靠左对齐显示、软件名称和图标图片居中对齐、作者名称和blog图片底部靠右对齐。注意在版本号图片显示ImageView控件下面添加一个RelativeLayout控件作为软件名称和图标图片ImageVIew和作者名称和blog图片ImageView的父控件用来控制居中对齐已经底部对齐的实现,具体代码如下:代码
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/layout" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ver" android:layout_marginTop="15dip" android:layout_marginLeft="15dip"> </ImageView> <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/logo" android:layout_centerInParent="true"> </ImageView> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/dev" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:layout_marginRight="5dip" android:layout_marginBottom="35dip"> </ImageView> </RelativeLayout> </LinearLayout>

三、在ec打开名为MainActivity的Activity源代码文件进行编辑,onCreate部分代码如下:

public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); }

然后运行项目可以在模拟器中显示,上面的几个图片都按照设计的位置和效果进行显示只是整个页面的背景还是黑色的,接下来就是背景部分的显示实现,由于为了实现横竖屏切换显示,背景图的显示采用代码进行控制显示,首先用如下方法获取当前手机是横屏还是竖屏:

//获取屏幕方向 public static int ScreenOrient(Activity activity) { int orient = activity.getRequestedOrientation(); if(orient != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE && orient != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { //宽>高为横屏,反正为竖屏 WindowManager windowManager = activity.getWindowManager(); Display display = windowManager.getDefaultDisplay(); int screenWidth = display.getWidth(); int screenHeight = display.getHeight(); orient = screenWidth < screenHeight ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; } return orient; }

然后编写一个名为AutoBackground的公共方法用来实现屏幕背景的自动切换,后面的几乎每一个功能页面都需要用到这个方法

public static void AutoBackground(Activity activity,View view,int Background_v, int Background_h) { int orient=ScreenOrient(activity); if (orient == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { //纵向 view.setBackgroundResource(Background_v); }else{ //横向 view.setBackgroundResource(Background_h); } }

完成上述两方法后在 MainActivity的onCreate方法中调用AutoBackground方法进行屏幕自动切换:

LinearLayout layout=(LinearLayout)findViewById(R.id.layout); //背景自动适应 AndroidHelper.AutoBackground(this, layout, R.drawable.bg_v, R.drawable.bg_h);

到此完成了载入页面的UI部分的实现,测试运行模拟器中查看效果,基本上跟最上面的设计效果图相符,测试效果图如下:

6天前 上传 下载附件 (94.6 KB)

通过上一篇文章(android开发我的新浪微博客户端-载入页面UI篇(1.1))已经完成了载入页面的UI部分的实现,效果如上图,接下来在上面的基础上完成载入页面的功能代码。

6天前 上传 下载附件 (94.6 KB)

  首先说明一下新浪微博提供了OAuth和Base OAuth两种认证方式(如果不知道什么是OAuth和Base OAuth请自己google一下恶补,同时接下来的2篇随笔也会对这方面进行详细的说明以及具体实现),本项目是采用OAuth认证方式,采用这种方式就需要有用户的新浪UserID、Access Token、Access Secret这3样东西才能自由便利的调用新浪的开放接口,本项目是这样做的当用户第一次使用软件时进行授权认证获取这3样东西的时候存储到sqlite库中以便用户下次使用时不需要重新进行繁琐的授权认证操作直接从sqlite库中读取出来即可,由于这样的需求载入页面的功能设定是这样:当用户打开软件显示载入页面时开始检查sqlite库中是否已经保存有用户的新浪微博的UserID号、Access Token、Access Secret的记录,如果一条记录都没有那就说明用户是第一次使用本软件那么跳到认证授权页面进行授权认证操作(认证授权功能在接下来的两篇中进行实现讲解)获取这3个值保存到sqlite库中,如果已经包括了记录,那么读取这些记录的UserID号、Access Token、Access Secret值然后根据这3个值调用新浪的api接口获取这些记录对应的用户昵称和用户头像图标等信息。

  上面功能设定中涉及到sqlite数据库的创建、数据表的创建、数据记录的添加、数据记录的读取等操作,这里新建名为SqliteHelper.java类文件提供sqlite数据表的创建、更新等,代码如下:

public class SqliteHelper extends SQLiteOpenHelper{ //用来保存 UserID、Access Token、Access Secret 的表名 public static final String TB_NAME="users"; public SqliteHelper(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version); } //创建表 @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE IF NOT EXISTS "+ TB_NAME+"("+ UserInfo.ID+" integer primary key,"+ UserInfo.USERID+" varchar,"+ UserInfo.TOKEN+" varchar,"+ UserInfo.TOKENSECRET+" varchar,"+ UserInfo.USERNAME+" varchar,"+ UserInfo.USERICON+" blob"+ ")" ); Log.e("Database","onCreate"); } //更新表 @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS " + TB_NAME); onCreate(db); Log.e("Database","onUpgrade"); } //更新列 public void updateColumn(SQLiteDatabase db, String oldColumn, String newColumn, String typeColumn){ try{ db.execSQL("ALTER TABLE " + TB_NAME + " CHANGE " + oldColumn + " "+ newColumn + " " + typeColumn ); }catch(Exception e){ e.printStackTrace(); } } }

接下来新建名为DataHelper.java类文件实现用户记录的创建、更新、删除等,代码如下

public class DataHelper { //数据库名称 private static String DB_NAME = "mysinaweibo.db"; //数据库版本 private static int DB_VERSION = 2; private SQLiteDatabase db; private SqliteHelper dbHelper; public DataHelper(Context context){ dbHelper=new SqliteHelper(context,DB_NAME, null, DB_VERSION); db= dbHelper.getWritableDatabase(); } public void Close() { db.close(); dbHelper.close(); } //获取users表中的UserID、Access Token、Access Secret的记录 public List<UserInfo> GetUserList(Boolean isSimple) { List<UserInfo> userList = new ArrayList<UserInfo>(); Cursor cursor=db.query(SqliteHelper.TB_NAME, null, null, null, null, null, UserInfo.ID+" DESC"); cursor.moveToFirst(); while(!cursor.isAfterLast()&& (cursor.getString(1)!=null)){ UserInfo user=new UserInfo(); user.setId(cursor.getString(0)); user.setUserId(cursor.getString(1)); user.setToken(cursor.getString(2)); user.setTokenSecret(cursor.getString(3)); if(!isSimple){ user.setUserName(cursor.getString(4)); ByteArrayInputStream stream = new ByteArrayInputStream(cursor.getBlob(5)); Drawable icon= Drawable.createFromStream(stream, "image"); user.setUserIcon(icon); } userList.add(user); cursor.moveToNext(); } cursor.close(); return userList; } //判断users表中的是否包含某个UserID的记录 public Boolean HaveUserInfo(String UserId) { Boolean b=false; Cursor cursor=db.query(SqliteHelper.TB_NAME, null, UserInfo.USERID + "=" + UserId, null, null, null,null); b=cursor.moveToFirst(); Log.e("HaveUserInfo",b.toString()); cursor.close(); return b; } //更新users表的记录,根据UserId更新用户昵称和用户图标 public int UpdateUserInfo(String userName,Bitmap userIcon,String UserId) { ContentValues values = new ContentValues(); values.put(UserInfo.USERNAME, userName); // BLOB类型 final ByteArrayOutputStream os = new ByteArrayOutputStream(); // 将Bitmap压缩成PNG编码,质量为100%存储 userIcon.compress(Bitmap.CompressFormat.PNG, 100, os); // 构造SQLite的Content对象,这里也可以使用raw values.put(UserInfo.USERICON, os.toByteArray()); int id= db.update(SqliteHelper.TB_NAME, values, UserInfo.USERID + "=" + UserId, null); Log.e("UpdateUserInfo2",id+""); return id; } //更新users表的记录 public int UpdateUserInfo(UserInfo user) { ContentValues values = new ContentValues(); values.put(UserInfo.USERID, user.getUserId()); values.put(UserInfo.TOKEN, user.getToken()); values.put(UserInfo.TOKENSECRET, user.getTokenSecret()); int id= db.update(SqliteHelper.TB_NAME, values, UserInfo.USERID + "=" + user.getUserId(), null); Log.e("UpdateUserInfo",id+""); return id; } //添加users表的记录 public Long SaveUserInfo(UserInfo user) { ContentValues values = new ContentValues(); values.put(UserInfo.USERID, user.getUserId()); values.put(UserInfo.TOKEN, user.getToken()); values.put(UserInfo.TOKENSECRET, user.getTokenSecret()); Long uid = db.insert(SqliteHelper.TB_NAME, UserInfo.ID, values); Log.e("SaveUserInfo",uid+""); return uid; } //删除users表的记录 public int DelUserInfo(String UserId){ int id= db.delete(SqliteHelper.TB_NAME, UserInfo.USERID +"="+UserId, null); Log.e("DelUserInfo",id+""); return id; } }

完成上面的代码后,我们需要在载入页面中调用上面的方法实现sqlite库中是否已经保存有用户的新浪微博的UserID号、Access Token、Access Secret的记录的功能在MainActivity的onCreate方法添加代码:

public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); ...... //获取账号列表 dbHelper=new DataHelper(this); List<UserInfo> userList= dbHelper.GetUserList(true); if(userList.isEmpty())//如果为空说明第一次使用跳到AuthorizeActivity页面进行OAuth认证 { Intent intent = new Intent(); intent.setClass(MainActivity.this, AuthorizeActivity.class); startActivity(intent); } else//如果不为空读取这些记录的UserID号、Access Token、Access Secret值 //然后根据这3个值调用新浪的api接口获取这些记录对应的用户昵称和用户头像图标等信息。 { for(UserInfo user:userList){ ...... } } }

本篇说说关于OAuth授权认证的事情,新浪开放api都必须在这个基础上才能调用,所以有必要专门来讲讲,前面的文章中已经提到过关于新浪微博提供了OAuth和Base OAuth两种认证方式,并且本项目采用OAuth认证方式,至于为什么采用这个OAuth认证而不采用Base OAuth认证原因很简单,自从Twitter只支持OAuth认证方式以来,各大应用都纷纷转向OAuth认证方式,而新浪微博的开放平台也将在近日停止Base OAuth的认证方式。

6天前 上传 下载附件 (30.41 KB)

OAuth的基本概念,OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是OAUTH的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此OAUTH是安全的。同样新浪微博提供OAuth认证也是为了保证用户账号和密码的安全,在这里通过OAuth建立普通新浪微博用户、客户端程序(我们正在开发的这个android客户端程序)、新浪微博三者之间的相互信任关系,让客户端程序(我们正在开发的这个android客户端程序)不需要知道用户的账号和密码也能浏览、发布微博,这样有效的保护了用户账号的安全性不需要把账号密码透露给客户端程序又达到了通过客户端程序写微博看微博目的。这个是OAuth的作用。

  结合新浪微博的OAuth认证来说说具体的功能实现,首先罗列一下关键字组,下面四组关键字跟我们接下来OAuth认证有非常大的关系。

  第一组:(App Key和App Secret),这组参数就是本系列文本第一篇提到的建一个新的应用获取App Key和App Secret。

  第二组:(Request Token和Request Secret)

  第三组:(oauth_verifier)

  第四组:(user_id、Access Token和Access Secret)

  新浪微博的OAuth认证过程,当用户第一次使用本客户端软件时,客户端程序用第一组作为参数向新浪微博发起请求,然后新浪微博经过验证后返回第二组参数给客户端软件同时表示新浪微博信任本客户端软件,当客户端软件获取第二组参数时作为参数引导用户浏览器跳至新浪微博的授权页面,然后用户在新浪的这个授权页面里输入自己的微博账号和密码进行授权,完成授权后根据客户端设定的回调地址把第三组参数返回给客户端软件并表示用户也信任本客户端软件,接下客户端软件把第二组参数和第三组参数作为参数再次向新浪微博发起请求,然后新浪微博返回第四组参数给客户端软件,第四组参数需要好好的保存起来这个就是用来代替用户的新浪账号和密码用的,在后面调用api时都需要。从这个过程来看用户只是在新浪微博的认证网页输入过账户和密码并没有在客户端软件里输入过账户和密码,客户端软件只保存了第四组数据并没有保存用户的账户和密码,这样有效的避免了账户和密码透露给新浪微博之外的第三方应用程序,保证 了安全性。

  本项目用为了方便开发采用了oauth-signpost开源项目进行OAuth认证开发,新建OAuth.java类文件对OA进行简单的封装,OAuth类主要有RequestAccessToken、GetAccessToken、SignRequest三个方法,第一个方法RequestAccessToken就是上面过程中用来获取第三组参数用的,GetAccessToken方法是用来获取第四组参数用,SignRequest方法是用来调用api用。由于采用了oauth-signpost开源项目简单了很多。具体代码如下:

public class OAuth { private CommonsHttpOAuthConsumer httpOauthConsumer; private OAuthProvider httpOauthprovider; public String consumerKey; public String consumerSecret; public OAuth() { // 第一组:(App Key和App Secret) // 这组参数就是本系列文本第一篇提到的建一个新的应用获取App Key和App Secret。 this("3315495489","e2731e7grf592c0fd7fea32406f86e1b"); } public OAuth(String consumerKey,String consumerSecret) { this.consumerKey=consumerKey; this.consumerSecret=consumerSecret; } public Boolean RequestAccessToken(Activity activity,String callBackUrl){ Boolean ret=false; try{ httpOauthConsumer = new CommonsHttpOAuthConsumer(consumerKey,consumerSecret); httpOauthprovider = new DefaultOAuthProvider("http://api.t.sina.com.cn/oauth/request_token","http://api.t.sina.com.cn/oauth/access_token","http://api.t.sina.com.cn/oauth/authorize"); String authUrl = httpOauthprovider.retrieveRequestToken(httpOauthConsumer, callBackUrl); activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(authUrl))); ret=true; }catch(Exception e){ } return ret; } public UserInfo GetAccessToken(Intent intent){ UserInfo user=null; Uri uri = intent.getData(); String verifier = uri.getQueryParameter(oauth.signpost.OAuth.OAUTH_VERIFIER); try { httpOauthprovider.setOAuth10a(true); httpOauthprovider.retrieveAccessToken(httpOauthConsumer,verifier); } catch (OAuthMessageSignerException ex) { ex.printStackTrace(); } catch (OAuthNotAuthorizedException ex) { ex.printStackTrace(); } catch (OAuthExpectationFailedException ex) { ex.printStackTrace(); } catch (OAuthCommunicationException ex) { ex.printStackTrace(); } SortedSet<String> user_id= httpOauthprovider.getResponseParameters().get("user_id"); String userId=user_id.first(); String userKey = httpOauthConsumer.getToken(); String userSecret = httpOauthConsumer.getTokenSecret(); user=new UserInfo(); user.setUserId(userId); user.setToken(userKey); user.setTokenSecret(userSecret); return user; } public HttpResponse SignRequest(String token,String tokenSecret,String url,List params) { HttpPost post = new HttpPost(url); //HttpClient httpClient = null; try{ post.setEntity(new UrlEncodedFormEntity(params,HTTP.UTF_8)); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } //关闭Expect:100-Continue握手 //100-Continue握手需谨慎使用,因为遇到不支持HTTP/1.1协议的服务器或者代理时会引起问题 post.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false); return SignRequest(token,tokenSecret,post); } public HttpResponse SignRequest(String token,String tokenSecret,HttpPost post){ httpOauthConsumer = new CommonsHttpOAuthConsumer(consumerKey,consumerSecret); httpOauthConsumer.setTokenWithSecret(token,tokenSecret); HttpResponse response = null; try { httpOauthConsumer.sign(post); } catch (OAuthMessageSignerException e) { e.printStackTrace(); } catch (OAuthExpectationFailedException e) { e.printStackTrace(); } catch (OAuthCommunicationException e) { e.printStackTrace(); } //取得HTTP response try { response = new DefaultHttpClient().execute(post); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return response; } }

这样就完成了OAuth功能类的开发,后面都会用到这个类相关的方法。本篇到这里就算是完结请继续关注后面的文章。

上一篇讲了讲OAuth授权认证的事情,大概的介绍了OAuth的原理,并且完成了一个OAuth.java的类库,提供了几个OAuth认证必要的方法,本篇开始具体讲本项目的用户授权功能,用户授权页面是当用户第一次使用本软件的时候自动从载入页面跳转过来的显示的页面,涉及OAuth认证相关都是在上一篇的OAuth.java的类基础上开发。用户授权页面分为UI篇和功能篇两篇,本篇先来讲讲UI的实现,这次就不贴PS的效果图了直接贴实现后的功能截图如下:

6天前 上传 下载附件 (84.47 KB)

  看上面的图,其实这个页面的UI实现不复杂,首先是背景部分的实现这个参考 android开发我的新浪微博客户端-载入页面UI篇(1.1),重点来讲讲这个半透明的弹出对话框窗口是如何实现的,首先新建名为AuthorizeActivity.java的Activity,并且在AndroidManifest.xml文件中添加这个Activity,这样这个Activity才能被使用,接下来为这个Activity新建名为authorize.xml的Layout,这个Layout很简单只负责logo小图标显示,背景部分和透明窗口都是有代码来实现,所以非常简单参考 android开发我的新浪微博客户端-载入页面UI篇(1.1)。

  完成Layout建立后在AuthorizeActivity的onCreate方法添加如下代码,设置authorize.xml为AuthorizeActivity的页面Layout:

@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.authorize); ....... }

接下来是本文的重点部分,半透明弹窗用Dialog控件进行实现,首先为这个半透明弹窗新建一个名为dialog.xml的Layout,这个Layout主要是对4个元素进行布局,如图所示分别为i小图标、信息提示、中间文字、开始按钮,首先用LinearLayout对i小图标和信息提示进行水平布局,中间文字以一个TextView跟在下面,对于开始按钮是用RelativeLayout进行底部对齐显示。具体代码如下:

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:padding="10dip"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/info_icon"> </ImageView> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="信息提示" android:textSize="13px" android:textColor="#219ac6" android:layout_marginLeft="5dip"> </TextView> </LinearLayout> <TextView android:id="@+id/text_info" android:layout_marginTop="6px" android:layout_width="200px" android:layout_height="wrap_content" android:textColor="#686767" android:textSize="14px" android:text="第一次使用需要输入您的新浪微博账号和密码进行登录授权"> </TextView> <RelativeLayout android:layout_width="fill_parent" android:layout_height="40px"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_centerHorizontal="true" android:layout_alignParentBottom="true"> <ImageButton android:id="@+id/btn_start" android:layout_width="80px" android:layout_height="31px" android:src="@drawable/btn_start_selector"> </ImageButton> <ImageButton android:id="@+id/btn_cancel" android:layout_width="80px" android:layout_height="31px" android:layout_marginLeft="8px" android:src="@drawable/btn_cancel_selector"> </ImageButton> </LinearLayout> </RelativeLayout> </LinearLayout>

完成了半透明弹窗的Layout定义接下来我们要做的就是为它写一个自定义样式来实现我们想要的显示效果,首先我们需准备一个圆角的半透明png图片名为dia_bg.png并且添加到drawable中,接下来再res/values文件夹新建名为 dialogStyle.xml的resources样式文件,具体代码如下:
<?xml version="1.0" encoding="utf-8"?> <resources> <mce:style name="dialog" parent="@android:style/Theme.Dialog"><!-- <item name="android:windowFrame">@null</item> <item name="android:windowIsFloating">true</item> <item name="android:windowIsTranslucent">false</item> <item name="android:windowNoTitle">true</item> <item name="android:windowBackground">@drawable/dia_bg</item> <item name="android:backgroundDimEnabled">false</item> --></mce:style><style name="dialog" parent="@android:style/Theme.Dialog" mce_bogus="1"><item name="android:windowFrame">@null</item> <item name="android:windowIsFloating">true</item> <item name="android:windowIsTranslucent">false</item> <item name="android:windowNoTitle">true</item> <item name="android:windowBackground">@drawable/dia_bg</item> <item name="android:backgroundDimEnabled">false</item></style> </resources>

这个样式文件的说明如下

  parent="@android:style/Theme.Dialog" :在系统Dialog样式基础上,相当于继承系统样式

  <item name="android:windowFrame">@null</item> :Dialog的windowFrame框为无

  <item name="android:windowIsFloating">true</item>:是否浮现在activity之上

  <item name="android:windowIsTranslucent">false</item>:是否半透明

  <item name="android:windowNoTitle">true</item>:是否显示title

  <item name="android:windowBackground">@drawable/dia_bg</item>:设置dialog的背景

  <item name="android:backgroundDimEnabled">false</item>: 背景是否模糊显示

  接下来写java代码把这个半透明弹窗显示出来,在AuthorizeActivity的onCreate方法添加如下代码:

...... View diaView=View.inflate(this, R.layout.dialog, null); dialog=new Dialog(AuthorizeActivity.this,R.style.dialog); dialog.setContentView(diaView); dialog.show(); ......

最后运行查看效果,到这里我们的任务已经完成了。请关注下一篇功能篇。

android开发我的新浪微博客户端-用户授权页面功能篇(3.2)

6天前 上传 下载附件 (30.41 KB)

6天前 上传 下载附件 (84.47 KB)


在上一篇实现了用户授权页面的UI,如上图,接下来要做的就是在这个基础上完成功能部分真正实现用户的授权认证,这一篇是android开发我的新浪微博客户端-OAuth篇(2.1)的具体应用篇原理就不多解释了不懂的看OAuth篇即可。认证过程从点击开始按钮然后跳转到新浪的授权页面,接着用户在新浪的页面里输入自己的账户和密码确定后返回用户授权页面。首先给开始按钮添加点击事件代码,代码中主要是调用我们前面android开发我的新浪微博客户端-OAuth篇(2.1)完成的OAuth类的RequestAccessToken方法用来获取oauth_verifier,具体代码如下:

ImageButton stratBtn=(ImageButton)diaView.findViewById(R.id.btn_start); stratBtn.setOnClickListener(new OnClickListener(){ @Override public void onClick(View arg0) { auth=new OAuth(); auth.RequestAccessToken(AuthorizeActivity.this, CallBackUrl); } });

上面的代码中重点来说明一下 RequestAccessToken方法的第二参数CallBackUrl,这个参数是用户在新浪的页面中输入账户密码后完成认证后返回的地址,我这里是这样设置的CallBackUrl = "myapp://AuthorizeActivity",在AndroidManifest.xml中配置给AuthorizeActivity添加如下配置把myapp://AuthorizeActivity指向到AuthorizeActivity,这样当页面返回到AuthorizeActivity中就可以获取到传过来的oauth_verifier参数。

<intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="myapp" android:host="AuthorizeActivity" /> </intent-filter>

再AuthorizeActivity如果来接收返回的oauth_verifier参数呢?接下来在AuthorizeActivity添加如下方法:
@Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); //在这里处理获取返回的oauth_verifier参数 }

关于onNewIntent的说明是这样的,onCreate是用来创建一个Activity也就是创建一个窗体,但一个Activty处于任务栈的顶端,若再次调用startActivity去创建它,则不会再次创建。若你想利用已有的Acivity去处理别的Intent时,你就可以利用onNewIntent来处理。在onNewIntent里面就会获得新的Intent,在这里AuthorizeActivity是属于已有的Acivity,所以需要onNewIntent来处理接收返回的参数,获取oauth_verifier参数后OAuth还没有结束从android开发我的新浪微博客户端-OAuth篇(2.1)描述来看还需要进行根据这个参数继续向新浪微博请求获取User_id、Access Token和Access Secret,在这里我把这些操作全部写在了GetAccessToken方法中。在onNewIntent添加如下代码:

UserInfo user= auth.GetAccessToken(intent); if(user!=null){ DataHelper helper=new DataHelper(this); String uid=user.getUserId(); if(helper.HaveUserInfo(uid)) { helper.UpdateUserInfo(user); Log.e("UserInfo", "update"); }else { helper.SaveUserInfo(user); Log.e("UserInfo", "add"); } }

通过上面的代码完成了User_id、Access Token和Access Secret 获取并且保存到了sqlite库中,这样就完成了用户的OAuth认证,当需要调用新浪的api时只需要去sqlite库中找该用户的User_id、Access Token和Access Secret 即可。到这里本篇就结束了,请关注下一篇。

android开发我的新浪微博客户端-登录页面UI篇(4.1)

6天前 上传 下载附件 (83.17 KB)

6天前 上传 下载附件 (85.15 KB)

首先回顾一下功能流程当用户开启软件显示载入页面时程序首先去sqlite库查询是否已经保存有用户的新浪微博的UserID号、Access Token、Access Secret的记录如果没有一条记录那么跳转到用户授权功能页面,这个已经由上面两篇文章实现了,如果有记录那么页面跳转到用户登录页面,也就是本篇以及下篇要实现的功能,本篇讲UI的实现,本项目支持多微博账号了,也就是用户可以设置多个微博账号,登录的时候选择其中的一个登录,具体效果如上图,新建名LoginActivity.java的Activity并且在AndroidManifest.xml中进行相应配置,这个页面就是我们要实现的用户登录页面。

看上面的效果,首先页面分3部分实现,背景部分、底部菜单部分、用户选择以及头像显示部分,首先在res/layout的目录下新建名为login.xml的layout,然后根据页面显示要求编写如下的布局控制:

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/layout" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/logo_s" android:layout_marginTop="5dip" android:layout_marginLeft="5dip"> </ImageView> <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent"> <RelativeLayout android:id="@+id/iconBtn" android:layout_width="90px" android:layout_height="80px" android:background="@drawable/icon_selector" android:layout_above="@+id/selectLayout" android:layout_centerHorizontal="true" android:layout_marginBottom="20dip"> <ImageView android:id="@+id/icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true"> </ImageView> </RelativeLayout> <RelativeLayout android:id="@+id/selectLayout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true"> <EditText android:id="@+id/iconSelect" android:layout_width="200px" android:layout_height="wrap_content" android:maxLength="10" android:paddingLeft="20px" android:editable="false" android:enabled="false" android:textSize="13px" android:background="@drawable/input_over" > </EditText> <ImageButton android:id="@+id/iconSelectBtn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="1.0dip" android:layout_alignTop="@+id/iconSelect" android:layout_alignRight="@+id/iconSelect" android:layout_alignBottom="@+id/iconSelect" android:background="@drawable/more_selector" > </ImageButton> <ImageButton android:id="@+id/login" android:layout_width="40px" android:layout_height="40px" android:layout_marginLeft="5dip" android:layout_alignTop="@+id/iconSelectBtn" android:layout_toRightOf="@+id/iconSelectBtn" android:layout_alignBottom="@+id/iconSelectBtn" android:background="@drawable/btn_in_selector" > </ImageButton> </RelativeLayout> <RelativeLayout android:layout_width="fill_parent" android:layout_height="44dip" android:layout_alignParentBottom="true" android:background="#BB768e95"> <LinearLayout android:id="@+id/addLayout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:layout_alignParentLeft="true" android:gravity="center" android:layout_marginTop="3px"> <ImageButton android:id="@+id/addIcon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/add_selector"> </ImageButton> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#ffffff" android:textSize="12px" android:text="添加账号"> </TextView> </LinearLayout> <LinearLayout android:id="@+id/exitLayout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:layout_centerInParent="true" android:gravity="center" android:layout_marginTop="3px"> <ImageButton android:id="@+id/exitIcon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/exit_selector"> </ImageButton> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#ffffff" android:textSize="12px" android:text="退出软件"> </TextView> </LinearLayout> <LinearLayout android:id="@+id/delLayout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:layout_alignParentRight="true" android:gravity="center" android:layout_marginTop="3px"> <ImageButton android:id="@+id/delIcon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/del_selector"> </ImageButton> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#ffffff" android:textSize="12px" android:text="删除账号"> </TextView> </LinearLayout> </RelativeLayout> </RelativeLayout> </LinearLayout>

正对上面的login.xml的layout进行一下说明,背景部分前面已经讲过了这里也就不重复。
  底部菜单实现,原本我是采用GridView实现的非常的方便但是后来由于显示位置不好控制改成了用RelativeLayout和LinearLayout嵌套的方式,实现的比较土但是达到了显示需求,首先是一个最外面的RelativeLayout目的是用来实现底部对齐显示,并且把这个RelativeLayout的背景设置为浅蓝色半透明的效果,关键这2行:android:layout_alignParentBottom="true"和android:background="#BB768e95"。然后是在RelativeLayout内部添加3个LinearLayout分别是用来显示添加账号、退出软件、删除账号3个功能按钮菜单,并且分别设置为左对齐、居中对齐、右对齐,3个LinearLayout都设置为垂直布局androidrientation="vertical",然后每LinearLayout添加相应的图片和文字。
  用户选择以及头像显示部分,这块分成3小块,用来显示用户头像的ImageView、用来显示用户名字并且点击可以出现选择列表的EditText、用来点击进入当前选择用户首页的功能按钮ImageButton,这3小块的布局实现也是采用elativeLayout和LinearLayout相互嵌套配合的方式实现的具体参考login.xml。这里重点说说这个账号选择列表弹出窗口的实现,当点击下拉箭头按钮的时候弹出并显示,这个是用Dialog控件实现,首先准备好圆角的半透明背景图mask_bg.png然后添加到res/drawable-mdpi文件夹下,接着自定义一个Dialog样式文件,在res/values目录下新建名为dialogStyles2.xml的resources文件,在用户授权验证页面的时候我们也自定义过类似的Dialog的样式,具体解释可以参考前面的户授权验证页面功能,内容如下:
<?xml version="1.0" encoding="utf-8"?> <resources> <mce:style name="dialog2" parent="@android:style/Theme.Dialog"><!-- <item name="android:windowFrame">@null</item> <item name="android:windowIsFloating">true</item> <item name="android:windowIsTranslucent">false</item> <item name="android:windowNoTitle">true</item> <item name="android:windowBackground">@drawable/mask_bg</item> <item name="android:backgroundDimEnabled">true</item> --></mce:style><style name="dialog2" parent="@android:style/Theme.Dialog" mce_bogus="1"><item name="android:windowFrame">@null</item> <item name="android:windowIsFloating">true</item> <item name="android:windowIsTranslucent">false</item> <item name="android:windowNoTitle">true</item> <item name="android:windowBackground">@drawable/mask_bg</item> <item name="android:backgroundDimEnabled">true</item></style> </resources>

接下来还需要定义选择列表的layout,新建名为dialog2.xml的layout文件,内容如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:padding="4dip"> <ListView android:id="@+id/list" android:layout_width="240px" android:layout_height="220px" android:divider="#f1f2f2" android:dividerHeight="1px" android:layout_margin="5px" android:background="#ffffff" android:cacheColorHint="#00000000"> </ListView> </LinearLayout>

完成了layout和样式文件的编写,接下来就是把dialogStyles2.xml样式文件和dialog2.xml的列表layout用起来,当点击id为iconSelectBtn的ImageButton时显示用户选择窗口,在LoginActivity的onCreate方法中添加如下代码:
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.login); LinearLayout layout=(LinearLayout)findViewById(R.id.layout); //背景自动适应 AndroidHelper.AutoBackground(this, layout, R.drawable.bg_v, R.drawable.bg_h); ImageButton iconSelectBtn=(ImageButton)findViewById(R.id.iconSelectBtn); iconSelectBtn.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { View diaView=View.inflate(LoginActivity.this, R.layout.dialog2, null); dialog=new Dialog(LoginActivity.this,R.style.dialog2); dialog.setContentView(diaView); dialog.show(); ...... } });

到这里登录的UI部分就实现的差不多了,剩下的都是一些功能部分代码用来实现从sqlite中账号列表的获取,以及点击选择等交互操作等,这些在下一篇中来继续的讲。

android开发我的新浪微博客户端-登录页面功能篇(4.2)

上一篇中完成了如上图的UI部分的实现,现在继续来讲功能的实现,用户登录操作主要就是账号列表显示和选择账号登录两个功能其他的都是些简单的辅助功能,首先是点击id为iconSelectBtn的ImageButton时显示用户选择窗口,这个时候去数据库中获取账号记录然后在选择窗口中以列表方式显示出来,通过上一篇已经知道Id为list的ListView控件来显示账号列表,首先是从数据库中获取所有的账户记录然后设置默认选中的用户账号代码如下:
private void initUser(){ //获取账号列表 dbHelper=new DataHelper(this); userList = dbHelper.GetUserList(false); if(userList.isEmpty()) { Intent intent = new Intent(); intent.setClass(LoginActivity.this, AuthorizeActivity.class); startActivity(intent); } else { SharedPreferences preferences = getSharedPreferences(Select_Name, Activity.MODE_PRIVATE); String str= preferences.getString("name", ""); UserInfo user=null; if(str!="") { user=GetUserByName(str); } if(user==null) { user=userList.get(0); } icon.setImageDrawable(user.getUserIcon()); iconSelect.setText(user.getUserName()); } }

这个initUser() 初始账号的方法在LoginActivity的onCreate中调用,主要完成两件事情,第一件获取通过userList = dbHelper.GetUserList(false);获取所有的账户记录,关于DataHelper前面已经有说过了,如果获取的用户记录为空那么就跳转到用户授权功能页面让用户添加账号,如果不为空那么通过SharedPreferences去读取用户上一次选择的账号名称,如果没有或者数据库里账号记录不包括这个账户名称那么默认显示记录的第一个账号和头像,如果有那么显示这个账户的名称和头像。关于SharedPreferences,是android提供给开发者用来存储一些简单的数据用的,非常方便类似于网站的Cookie,在这里我就是用这个来保存上一次用户选择的是哪个账号,非常实用。
接下类首先为Id为list的ListView控件准备数据Adapter,这个Adapter非常简单就是普通的adapter继承BaseAdapter即可,代码如下:
public class UserAdapater extends BaseAdapter{ @Override public int getCount() { return userList.size(); } @Override public Object getItem(int position) { return userList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { convertView = LayoutInflater.from(getApplicationContext()).inflate(R.layout.item_user, null); ImageView iv = (ImageView) convertView.findViewById(R.id.iconImg); TextView tv = (TextView) convertView.findViewById(R.id.showName); UserInfo user = userList.get(position); try { //设置图片显示 iv.setImageDrawable(user.getUserIcon()); //设置信息 tv.setText(user.getUserName()); } catch (Exception e) { e.printStackTrace(); } return convertView; }

接下就是为这个ListView设定数据源Adapter,在账号选择窗口显示的时候进行设置,添加到id为iconSelectBtn的ImageButton的OnClickListener中代码如下:
ImageButton iconSelectBtn=(ImageButton)findViewById(R.id.iconSelectBtn); iconSelectBtn.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { ...... dialog.show(); UserAdapater adapater = new UserAdapater(); ListView listview=(ListView)diaView.findViewById(R.id.list); listview.setVerticalScrollBarEnabled(false);// ListView去掉下拉条 listview.setAdapter(adapater); listview.setOnItemClickListener(new OnItemClickListener(){ @Override public void onItemClick(AdapterView<?> arg0, View view,int arg2, long arg3) { TextView tv=(TextView)view.findViewById(R.id.showName); iconSelect.setText(tv.getText()); ImageView iv=(ImageView)view.findViewById(R.id.iconImg); icon.setImageDrawable(iv.getDrawable()); dialog.dismiss(); } }); } });

通过上面代码完成了账号选择的功能,接下来给id为login的ImageButton添加OnClickListener,使得点击后以当前选择账号进入微博首页,代码如下:
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.login); ...... ImageButton login=(ImageButton)findViewById(R.id.login); login.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { GoHome(); } }); } //进入用户首页 private void GoHome(){ if(userList!=null) { String name=iconSelect.getText().toString(); UserInfo u=GetUserByName(name); if(u!=null) { ConfigHelper.nowUser=u;//获取当前选择的用户并且保存 } } if(ConfigHelper.nowUser!=null) { //进入用户首页 Intent intent = new Intent(); intent.setClass(LoginActivity.this, HomeActivity.class); startActivity(intent); } }

在上面的GoHome方法中ConfigHelper.nowUser是类型为UserInfo的static类型用来保存当前登录账号的信息,替代web中session使用。
最后添加如下方法,用来当这个登录LoginActivity结束的时候保存当前选择的账户名称到SharedPreferences中,以便帮用户记住登录账号的功能,就是前面的initUser() 初始账号的方法中会获取保存在SharedPreferences中的账户名称,代码如下:
@Override protected void onStop() { //获得SharedPreferences对象 SharedPreferences MyPreferences = getSharedPreferences(Select_Name, Activity.MODE_PRIVATE); //获得SharedPreferences.Editor对象 SharedPreferences.Editor editor = MyPreferences.edit(); //保存组件中的值 editor.putString("name", iconSelect.getText().toString()); editor.commit(); super.onStop(); }

至此登录页面功能篇结束,请继续关注下一篇。

android开发我的新浪微博客户端-用户首页面UI篇(5.1)

6天前 上传 下载附件 (101.43 KB)

在前篇完成了用户登录功能后开始用户首页的开发,用户的首页主要的内容是当前登录用户关注的微博列表,本篇先来讲讲UI的实现,效果如上图,整个页面分为上、中、下三部分,上面部分是工具条,显示当前登录用户的昵称以及写微博、刷新两个功能按钮;中间部分是当前用户关注的最新微博列表,下面部分是功能切换栏,用来进行各个功能之间的切换。

首先新建名为HomeActivity.java的Activity作为用户首页,然后在res/layout目录下新建名为home.xml的Layout,具体代码如下:

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/layout" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <RelativeLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_margin="3px"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/logo_ss"> </ImageView> <TextView android:id="@+id/showName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:textColor="#343434" android:textSize="15px"> </TextView> <ImageButton android:id="@+id/writeBtn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toLeftOf="@+id/refreshBtn" android:background="@drawable/btn_write_selector"> </ImageButton> <ImageButton android:id="@+id/refreshBtn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_marginLeft="12px" android:background="@drawable/btn_refresh_selector"> </ImageButton> </RelativeLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="@drawable/hr"> </LinearLayout> <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent"> <ListView android:id="@+id/Msglist" android:layout_width="fill_parent" android:layout_height="match_parent" android:divider="@drawable/divider" android:dividerHeight="2px" android:layout_margin="0px" android:background="#BBFFFFFF" android:cacheColorHint="#00000000" android:layout_above="@+id/toolbarLayout" android:fastScrollEnabled="true" android:focusable="true"> </ListView> <LinearLayout android:id="@+id/loadingLayout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:visibility="invisible" android:layout_centerInParent="true"> <ProgressBar android:id="@+id/loading" android:layout_width="31px" android:layout_height="31px" android:layout_gravity="center" style="@style/progressStyle"> </ProgressBar> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="正在载入" android:textSize="12px" android:textColor="#9c9c9c" android:layout_gravity="center" android:layout_below="@+id/loading"> </TextView> </LinearLayout> <LinearLayout android:id="@+id/toolbarLayout" android:layout_width="fill_parent" android:layout_height="44dip" android:layout_alignParentBottom="true"> </LinearLayout> </RelativeLayout> </LinearLayout>

这个布局首先是一个竖直的根LinearLayout,在这个根LinearLayout里面分别是两个RelativeLayout, 第一个RelativeLayout 用来显示页面的工具条,第二个RelativeLayout用来显示列表以及底部的功能栏,特别主要在这第二个RelativeLayout中有一个id为loadingLayout的LinearLayout是用来显示数据载入中的动画,它的android:visibility属性为invisible(也可以设置成gone,区别:invisible这个View在ViewGroupt中仍保留它的位置,不重新layout
gone>不可见,但这个View在ViewGroupt中不保留位置,重新layout,那后面的view就会取代他的位置。 ),也就是一开始不显示的意思,接下来看看
<ProgressBar

android:id="@+id/loading"
android:layout_width="31px"
android:layout_height="31px"

android:layout_gravity="center"

style="@style/progressStyle">
</ProgressBar>
这个ProgressBar控件就是用来显示动画用的,关键就是 style="@style/progressStyle",在res/values目录下新建名为loadingstyles.xml,内容如下:
<?xml version="1.0" encoding="UTF-8"?> <resources> <mce:style name="progressStyle" width="38" height="38" parent="@android:style/Widget.ProgressBar.Small"><!-- <item name="android:indeterminateDrawable">@anim/loading</item> --></mce:style><style name="progressStyle" width="38" height="38" parent="@android:style/Widget.ProgressBar.Small" mce_bogus="1"><item name="android:indeterminateDrawable">@anim/loading</item></style> </resources>

接着准备好r1.png - r8.png,

6天前 上传 下载附件 (998 Bytes)

6天前 上传 下载附件 (980 Bytes)

6天前 上传 下载附件 (1013 Bytes)

6天前 上传 下载附件 (1014 Bytes)

6天前 上传 下载附件 (986 Bytes)

6天前 上传 下载附件 (992 Bytes)

6天前 上传 下载附件 (1010 Bytes)

6天前 上传 下载附件 (1 KB)

八张不同的小图片分别代表每旋转45度图片,八张刚好是360度。把这些图片添加到res/drawable-mdpi目录中。然后在res/anim目录下新建名为loading.xml动画文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?> <animation-list android:oneshot="false" xmlns:android="http://schemas.android.com/apk/res/android"> <item android:duration="200" android:drawable="@drawable/r1" /> <item android:duration="200" android:drawable="@drawable/r2" /> <item android:duration="200" android:drawable="@drawable/r3" /> <item android:duration="200" android:drawable="@drawable/r4" /> <item android:duration="200" android:drawable="@drawable/r5" /> <item android:duration="200" android:drawable="@drawable/r6" /> <item android:duration="200" android:drawable="@drawable/r7" /> <item android:duration="200" android:drawable="@drawable/r8" /> </animation-list>

关于Android播放动画实现我是参考http://www.eoeandroid.com/forum.php?mod=viewthread&tid=67311&extra=
本篇到这里就结束了,下一篇继续讲用户首页的功能实现,请关注。

android开发我的新浪微博客户端-用户首页面功能篇(5.2)

6天前 上传 下载附件 (101.43 KB)


上一篇完成用户首页的UI实现,本篇接下来讲功能部分的实现,本页面主要的功能就用户关注的最新微博列表,从上一篇中知道本列表是用ID为Msglist的ListView控件来实现,本篇的主要就讲解如果获取微博列表数据给这个ListView提供显示数据。ListView每一条子数据分别由用户头像、用户昵称、发布时间、是否包含照片、微博内容这五部分组成,根据这五部分定义一个名为WeiBoInfo.java实体类,代码如下:
public class WeiBoInfo { //文章id private String id; public String getId(){ return id; } public void setId(String id){ this.id=id; } //发布人id private String userId; public String getUserId(){ return userId; } public void setUserId(String userId){ this.userId=userId; } //发布人名字 private String userName; public String getUserName(){ return userName; } public void setUserName(String userName){ this.userName=userName; } //发布人头像 private String userIcon; public String getUserIcon(){ return userIcon; } public void setUserIcon(String userIcon){ this.userIcon=userIcon; } //发布时间 private String time; public String getTime(){ return time; } public void setTime(String time) { this.time=time; } //是否有图片 private Boolean haveImage=false; public Boolean getHaveImage(){ return haveImage; } public void setHaveImage(Boolean haveImage){ this.haveImage=haveImage; } //文章内容 private String text; public String getText(){ return text; } public void setText(String text){ this.text=text; } }

然后在res/layout目录下新建名为weibo.xml的Layout用来控制ListView子项的显示部件,代码很简单不多解释了,直接看下面代码:

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal"> <ImageView android:id="@+id/wbicon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/usericon" android:layout_margin="8px"> </ImageView> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" android:paddingLeft="0px" android:paddingRight="5px" android:layout_marginTop="5px" android:layout_marginBottom="5px"> <RelativeLayout android:layout_width="fill_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/wbuser" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="15px" android:textColor="#424952" android:layout_alignParentLeft="true"> </TextView> <ImageView android:id="@+id/wbimage" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="3px" android:layout_marginRight="5px" android:layout_toLeftOf="@+id/wbtime"> </ImageView> <TextView android:id="@+id/wbtime" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:textColor="#f7a200" android:textSize="12px"> </TextView> </RelativeLayout> <TextView android:id="@+id/wbtext" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#424952" android:textSize="13px" android:layout_marginTop="4px"> </TextView> </LinearLayout> </LinearLayout>

接下来为列表控件定义一个数据Adapter,代码如下:
private List<WeiBoInfo> wbList; //微博列表Adapater public class WeiBoAdapater extends BaseAdapter{ private AsyncImageLoader asyncImageLoader; @Override public int getCount() { return wbList.size(); } @Override public Object getItem(int position) { return wbList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { asyncImageLoader = new AsyncImageLoader(); convertView = LayoutInflater.from(getApplicationContext()).inflate(R.layout.weibo, null); WeiBoHolder wh = new WeiBoHolder(); wh.wbicon = (ImageView) convertView.findViewById(R.id.wbicon); wh.wbtext = (TextView) convertView.findViewById(R.id.wbtext); wh.wbtime = (TextView) convertView.findViewById(R.id.wbtime); wh.wbuser = (TextView) convertView.findViewById(R.id.wbuser); wh.wbimage=(ImageView) convertView.findViewById(R.id.wbimage); WeiBoInfo wb = wbList.get(position); if(wb!=null){ convertView.setTag(wb.getId()); wh.wbuser.setText(wb.getUserName()); wh.wbtime.setText(wb.getTime()); wh.wbtext.setText(wb.getText(), TextView.BufferType.SPANNABLE); textHighlight(wh.wbtext,new char[]{'#'},new char[]{'#'}); textHighlight(wh.wbtext,new char[]{'@'},new char[]{':',' '}); textHighlight2(wh.wbtext,"http://"," "); if(wb.getHaveImage()){ wh.wbimage.setImageResource(R.drawable.images); } Drawable cachedImage = asyncImageLoader.loadDrawable(wb.getUserIcon(),wh.wbicon, new ImageCallback(){ @Override public void imageLoaded(Drawable imageDrawable,ImageView imageView, String imageUrl) { imageView.setImageDrawable(imageDrawable); } }); if (cachedImage == null) { wh.wbicon.setImageResource(R.drawable.usericon); }else{ wh.wbicon.setImageDrawable(cachedImage); } } return convertView; }

上面的这个Adapter实现没有什么特别的很普通,不过这个中使用了AsyncImageLoader的方法,这个是用来实现用户头像图标的异步载入显示,这样能提高列表显示的速度,提高用户体验,AsyncImageLoader的代码如下:
public class AsyncImageLoader { //SoftReference是软引用,是为了更好的为了系统回收变量 private HashMap<String, SoftReference<Drawable>> imageCache; public AsyncImageLoader() { imageCache = new HashMap<String, SoftReference<Drawable>>(); } public Drawable loadDrawable(final String imageUrl,final ImageView imageView, final ImageCallback imageCallback){ if (imageCache.containsKey(imageUrl)) { //从缓存中获取 SoftReference<Drawable> softReference = imageCache.get(imageUrl); Drawable drawable = softReference.get(); if (drawable != null) { return drawable; } } final Handler handler = new Handler() { public void handleMessage(Message message) { imageCallback.imageLoaded((Drawable) message.obj, imageView,imageUrl); } }; //建立新一个新的线程下载图片 new Thread() { @Override public void run() { Drawable drawable = loadImageFromUrl(imageUrl); imageCache.put(imageUrl, new SoftReference<Drawable>(drawable)); Message message = handler.obtainMessage(0, drawable); handler.sendMessage(message); } }.start(); return null; } public static Drawable loadImageFromUrl(String url){ URL m; InputStream i = null; try { m = new URL(url); i = (InputStream) m.getContent(); } catch (MalformedURLException e1) { e1.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } Drawable d = Drawable.createFromStream(i, "src"); return d; } //回调接口 public interface ImageCallback { public void imageLoaded(Drawable imageDrawable,ImageView imageView, String imageUrl); } }

完成上述的工作后,接下来就是显示微薄列表, 在HomeActivity的onCreate方法中调用loadList();代码如下:
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.home); 。。。。。。 loadList(); } private void loadList(){ if(ConfigHelper.nowUser==null) { } else { user=ConfigHelper.nowUser; //显示当前用户名称 TextView showName=(TextView)findViewById(R.id.showName); showName.setText(user.getUserName()); OAuth auth=new OAuth(); String url = "http://api.t.sina.com.cn/statuses/friends_timeline.json"; List params=new ArrayList(); params.add(new BasicNameValuePair("source", auth.consumerKey)); HttpResponse response =auth.SignRequest(user.getToken(), user.getTokenSecret(), url, params); if (200 == response.getStatusLine().getStatusCode()){ try { InputStream is = response.getEntity().getContent(); Reader reader = new BufferedReader(new InputStreamReader(is), 4000); StringBuilder buffer = new StringBuilder((int) response.getEntity().getContentLength()); try { char[] tmp = new char[1024]; int l; while ((l = reader.read(tmp)) != -1) { buffer.append(tmp, 0, l); } } finally { reader.close(); } String string = buffer.toString(); //Log.e("json", "rs:" + string); response.getEntity().consumeContent(); JSONArray data=new JSONArray(string); for(int i=0;i<data.length();i++) { JSONObject d=data.getJSONObject(i); //Log.e("json", "rs:" + d.getString("created_at")); if(d!=null){ JSONObject u=d.getJSONObject("user"); if(d.has("retweeted_status")){ JSONObject r=d.getJSONObject("retweeted_status"); } //微博id String id=d.getString("id"); String userId=u.getString("id"); String userName=u.getString("screen_name"); String userIcon=u.getString("profile_image_url"); Log.e("userIcon", userIcon); String time=d.getString("created_at"); String text=d.getString("text"); Boolean haveImg=false; if(d.has("thumbnail_pic")){ haveImg=true; //String thumbnail_pic=d.getString("thumbnail_pic"); //Log.e("thumbnail_pic", thumbnail_pic); } Date date=new Date(time); time=ConvertTime(date); if(wbList==null){ wbList=new ArrayList<WeiBoInfo>(); } WeiBoInfo w=new WeiBoInfo(); w.setId(id); w.setUserId(userId); w.setUserName(userName); w.setTime(time); w.setText(text); w.setHaveImage(haveImg); w.setUserIcon(userIcon); wbList.add(w); } } }catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (JSONException e) { e.printStackTrace(); } } if(wbList!=null) { WeiBoAdapater adapater = new WeiBoAdapater(); ListView Msglist=(ListView)findViewById(R.id.Msglist); Msglist.setOnItemClickListener(new OnItemClickListener(){ @Override public void onItemClick(AdapterView<?> arg0, View view,int arg2, long arg3) { Object obj=view.getTag(); if(obj!=null){ String id=obj.toString(); Intent intent = new Intent(HomeActivity.this,ViewActivity.class); Bundle b=new Bundle(); b.putString("key", id); intent.putExtras(b); startActivity(intent); } } }); Msglist.setAdapter(adapater); } } loadingLayout.setVisibility(View.GONE); }

上面的loadList() 方法通过新浪Api接口http://api.t.sina.com.cn/statuses/friends_timeline.json获取当前登录用户及其所关注用户的最新微博消息,然后显示到列表中。
这样就完成了用户首页功能的开发。

android开发我的新浪微博客户端-阅读微博UI篇(6.1)

6天前 上传 下载附件 (154.98 KB)

上一篇完成了微博列表的功能,本篇接着做预读微博的功能,本篇主要讲讲UI部分的实现,最终实现的效果如上图所示。整个显示页面从上往下分为四部分,第一部分顶部工具条、第二部分作者头像和名称、第三部分微博正文、第四部分功能按钮区。新建名为ViewActivity.java作为阅读微博的页面,再res/layout目录下新建名为view.xml的Layout,代码如下:

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/layout" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <RelativeLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_margin="3px"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/logo_ss"> </ImageView> <TextView android:id="@+id/showName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:textColor="#343434" android:text="阅读微博" android:textSize="16px"> </TextView> <ImageButton android:id="@+id/returnBtn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toLeftOf="@+id/homeBtn" android:background="@drawable/bnt_return_selector"> </ImageButton> <ImageButton android:id="@+id/homeBtn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_marginLeft="12px" android:background="@drawable/btn_home_selector"> </ImageButton> </RelativeLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="@drawable/hr"> </LinearLayout> <RelativeLayout android:id="@+id/user_bg" android:layout_width="fill_parent" android:layout_height="78px" android:paddingTop="8px" android:paddingLeft="15px" android:background="@drawable/u_bg_v"> <ImageView android:id="@+id/user_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:src="@drawable/usericon"> </ImageView> <TextView android:id="@+id/user_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@+id/user_icon" android:layout_marginLeft="10px" android:layout_marginTop="18px" android:textColor="#000000"> </TextView> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_marginRight="5px" android:layout_marginTop="10px" android:src="@drawable/sjjt"> </ImageView> </RelativeLayout> <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent"> <ScrollView android:layout_width="fill_parent" android:layout_height="fill_parent" android:paddingLeft="17px" android:paddingRight="17px" android:paddingBottom="5px" android:layout_above="@+id/menu_layout"> <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical"> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#000000" android:textSize="15px"> </TextView> <ImageView android:id="@+id/pic" android:layout_width="wrap_content" android:layout_height="wrap_content"> </ImageView> </LinearLayout> </ScrollView> <LinearLayout android:id="@+id/loadingLayout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:visibility="gone" android:layout_centerInParent="true"> <ProgressBar android:id="@+id/loading" android:layout_width="31px" android:layout_height="31px" android:layout_gravity="center" style="@style/progressStyle"> </ProgressBar> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="正在载入" android:textSize="12px" android:textColor="#9c9c9c" android:layout_gravity="center" android:layout_below="@+id/loading"> </TextView> </LinearLayout> <TableLayout android:id="@+id/menu_layout" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center" android:layout_alignParentBottom="true" android:layout_marginBottom="5px"> <TableRow android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center"> <Button android:id="@+id/btn_gz" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#3882b8" android:textSize="15px" android:text=" 关注(1231)" android:background="@drawable/lt_selector"> </Button> <Button android:id="@+id/btn_pl" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#3882b8" android:textSize="15px" android:text=" 评论(31)" android:background="@drawable/rt_selector"> </Button> </TableRow> <TableRow android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#3882b8" android:textSize="15px" android:layout_gravity="left" android:text="刷新" android:background="@drawable/lb_selector"> </Button> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#3882b8" android:textSize="15px" android:text="收藏" android:background="@drawable/rb_selector"> </Button> </TableRow> </TableLayout> </RelativeLayout> </LinearLayout>

上面这个布局实现起来并不复杂, 主要看看功能按钮区的4个按钮的点击上去的切换背景的效果,以关注按钮为例子看这行设置,android:background="@drawable/lt_selector",在res/drawable-mdpi目录下新建名为lt_selector.xml用来实现点击上去切换图片的效果,具体代码如下:
<?xml version="1.0" encoding="UTF-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_focused="false" android:state_selected="false" android:state_pressed="false" android:drawable="@drawable/tbtn_1" /> <item android:state_pressed="true" android:drawable="@drawable/tbtn_h_1" /> </selector>

本篇虽然看layout文件非常的长,其实仔细看看非常的简单了没有什么难和复杂的了,就是按照前面的经验控制好图片以及控件的显示位置和样式即可,本篇中用了一个ScrollView控件这个是前面没有用到过的,主要是用来当微博的内容超出显示区域的时候出现滚动条用的这个非常容易使用,所以就简单写一下到此结束了,请继续关注下一篇阅读微博的功能篇。


android开发我的新浪微博客户端-阅读微博功能篇(6.2)

6天前 上传 下载附件 (154.98 KB)

注:最近由于OAuth上传图片碰到了难题,一直在做这方面的研究导致博客很久没有更新。

  在上面一篇中已经实现了预读微博的UI界面,效果如上图,接下来完成功能部分的代码,当用户在上一个列表界面的列表中点击某一条微博的时候显示这个阅读微博的界面,在这个界面中根据传来的微博ID,然后根据这个ID通过api获取微博的具体内容进行显示。

  在ViewActivity.class的onCreate方法中添加如下代码:

private UserInfo user; private String key=""; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.view); 。。。。。 //获取上一个页面传递过来的key,key为某一条微博的id Intent i=this.getIntent(); if(!i.equals(null)){ Bundle b=i.getExtras(); if(b!=null){ if(b.containsKey("key")){ key = b.getString("key"); view(key); } } } }

 接下来就是view方法具体获取微博内容的方法,在这个方法中如果获取的本条微博如果包含图片那么就用前面AsyncImageLoader的方法异步载入图片并且进行显示,同时在这个方法中还要获取本条微博被转发的次数以及评论的次数,具体代码如下:
private void view(String id){ user=ConfigHelper.nowUser; OAuth auth=new OAuth(); String url = "http://api.t.sina.com.cn/statuses/show/:id.json"; List params=new ArrayList(); params.add(new BasicNameValuePair("source", auth.consumerKey)); params.add(new BasicNameValuePair("id", id)); HttpResponse response =auth.SignRequest(user.getToken(), user.getTokenSecret(), url, params); if (200 == response.getStatusLine().getStatusCode()){ try { InputStream is = response.getEntity().getContent(); Reader reader = new BufferedReader(new InputStreamReader(is), 4000); StringBuilder buffer = new StringBuilder((int) response.getEntity().getContentLength()); try { char[] tmp = new char[1024]; int l; while ((l = reader.read(tmp)) != -1) { buffer.append(tmp, 0, l); } } finally { reader.close(); } String string = buffer.toString(); //Log.e("json", "rs:" + string); response.getEntity().consumeContent(); JSONObject data=new JSONObject(string); if(data!=null){ JSONObject u=data.getJSONObject("user"); String userName=u.getString("screen_name"); String userIcon=u.getString("profile_image_url"); Log.e("userIcon", userIcon); String time=data.getString("created_at"); String text=data.getString("text"); TextView utv=(TextView)findViewById(R.id.user_name); utv.setText(userName); TextView ttv=(TextView)findViewById(R.id.text); ttv.setText(text); ImageView iv=(ImageView)findViewById(R.id.user_icon); AsyncImageLoader asyncImageLoader = new AsyncImageLoader(); Drawable cachedImage = asyncImageLoader.loadDrawable(userIcon,iv, new ImageCallback(){ @Override public void imageLoaded(Drawable imageDrawable,ImageView imageView, String imageUrl) { imageView.setImageDrawable(imageDrawable); } }); if (cachedImage == null) { iv.setImageResource(R.drawable.usericon); } else { iv.setImageDrawable(cachedImage); } if(data.has("bmiddle_pic")){ String picurl=data.getString("bmiddle_pic"); String picurl2=data.getString("original_pic"); ImageView pic=(ImageView)findViewById(R.id.pic); pic.setTag(picurl2); pic.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Object obj=v.getTag(); Intent intent = new Intent(ViewActivity.this,ImageActivity.class); Bundle b=new Bundle(); b.putString("url", obj.toString()); intent.putExtras(b); startActivity(intent); } }); Drawable cachedImage2 = asyncImageLoader.loadDrawable(picurl,pic, new ImageCallback(){ @Override public void imageLoaded(Drawable imageDrawable,ImageView imageView, String imageUrl) { showImg(imageView,imageDrawable); } }); if (cachedImage2 == null) { //pic.setImageResource(R.drawable.usericon); } else { showImg(pic,cachedImage2); } } } }catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (JSONException e) { e.printStackTrace(); } } url = "http://api.t.sina.com.cn/statuses/counts.json"; params=new ArrayList(); params.add(new BasicNameValuePair("source", auth.consumerKey)); params.add(new BasicNameValuePair("ids", id)); response =auth.SignRequest(user.getToken(), user.getTokenSecret(), url, params); if (200 == response.getStatusLine().getStatusCode()){ try { InputStream is = response.getEntity().getContent(); Reader reader = new BufferedReader(new InputStreamReader(is), 4000); StringBuilder buffer = new StringBuilder((int) response.getEntity().getContentLength()); try { char[] tmp = new char[1024]; int l; while ((l = reader.read(tmp)) != -1) { buffer.append(tmp, 0, l); } } finally { reader.close(); } String string = buffer.toString(); response.getEntity().consumeContent(); JSONArray data=new JSONArray(string); if(data!=null){ if(data.length()>0){ JSONObject d=data.getJSONObject(0); String comments=d.getString("comments"); String rt=d.getString("rt"); Button btn_gz=(Button)findViewById(R.id.btn_gz); btn_gz.setText(" 转发("+rt+")"); Button btn_pl=(Button)findViewById(R.id.btn_pl); btn_pl.setText(" 评论("+comments+")"); } } } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (JSONException e) { e.printStackTrace(); } } }

在上面的方法中对于微博中包含的图片显示尺寸进行了特别的处理,如果直接把获取的图片显示在ImageView中,因为当图片宽高超过手机屏幕的时候,系统会自动按照手机的屏幕按比例缩放图片进行显示,但是我发现一个现象图片的高虽然是按照比例缩小了,但是图片占据的高仍旧是原来图片的高度照成真实图片和文字内容之间多了很高的一块空白,这个现象非常的奇怪,所以我写了如下方法进行处理:
private void showImg(ImageView view,Drawable img){ int w=img.getIntrinsicWidth(); int h=img.getIntrinsicHeight(); Log.e("w", w+"/"+h); if(w>300) { int hh=300*h/w; Log.e("hh", hh+""); LayoutParams para=view.getLayoutParams(); para.width=300; para.height=hh; view.setLayoutParams(para); } view.setImageDrawable(img); }

本篇到这里就结束了,请继续关注下一篇。

关于微博服务端API的OAuth认证实现

6天前 上传 下载附件 (18.96 KB)

新浪微博跟update相关的api已经挂了很多天了一直没有恢复正常,返回错误:40070 Error limited application access api!,新浪开放平台的论坛里n多的人都在等这个恢复,新浪官方也相当的恶心出问题了连个公告都没有,既不说什么原因又不说什么时候能恢复。还是有版主说是api正在升级礼拜1恢复正常今天都礼拜2了还是不行。基于这个原因我的android版的新浪微博客户端已经停工好几天了,刚好是跟update相关的一些功能。 客户端开发不成了,就自己做做服务端程序,提供类似新浪微博rest api服务, api其实说简单也很简单了,无法是通过链接对外提供json或者xml格式的数据和接收外部提供的数据进去相应的存储、删除、更新等操作。过程中碰到的最麻烦的问题就是OAuth认证功能了,在做android版的新浪微博客户端时候也花了蛮长的时间对OAuth认证进行研究,在客户端原先是采用oauth-signpost开源项目,后来由于某些原因就放弃了这个开源类库,自己重新写了OAuth认证部分的实现, 现在做服务端的OAuth认证,其实有过做客户端的经验做服务端也差不多,简单的说无非是客户端对参数字符串进行签名然后把签名值传输到服务端,服务端也对同样对参数字符串进行签名,把从客户端传过来的签名值进去比较,简单的说就这么个过程,具体实现肯定比这个要复杂多了,不明真相的同学可以google一下OAuth进行深入的学习研究了。 服务端程序用asp.net和C#编写了而非java,理由很简单本人对.net更加熟悉。由于想快速的实现效果采用了oauth-dot-net开源项目并没有全部自己写。

一、首先新建名为Rest Api的ASP.NET Web应用程序,然后添加 oauth-dot-net开源项目相关的几个dll(Castle.Core.dll、Castle.MicroKernel.dll、Castle.Windsor.dll、CommonServiceLocator.WindsorAdapter.dll、Microsoft.Practices.ServiceLocation.dll、OAuth.Net.Common.dll、OAuth.Net.Components.dll、OAuth.Net.ServiceProvider.dll)。

二、在Web.config文件里添加相应的配置,具体可以参考OAuth.Net.Examples.EchoServiceProvider项目,然后在Global.asax.cs添加如下代码:

public override void Init() { IServiceLocator injector = new WindsorServiceLocator( new WindsorContainer( new XmlInterpreter( new ConfigResource("oauth.net.components")))); ServiceLocator.SetLocatorProvider(() => injector); }

接下来是比较重要,就是request_token、authorize、access_token的实现,OAuth认证实现的几个过程,不理解可以看android开发我的新浪微博客户端-OAuth篇(2.1) ,具体代码实现很多是参考OAuth.Net.Examples.EchoServiceProvider示例项目。
三、 首先新建ConsumerStore.cs类,用来存储Consumer信息,由于测试项目所以存储在内存中并没有考虑保存到数据库,真实项目的时候请把相应的Consumer信息保存到数据库中。Consumer信息对应新浪微博其实就是应用的App Key和App Secret,当开发者在新浪微博建一个新的应用获取App Key和App Secret,所以完整的应该还需要一个开发一个提供给第三方开发者申请获取App Key和App Secret的功能页面,这里就不具体实现,直接在代码里写死了一个名为测试应用的Consumer,App Key:2433927322,App Secret:87f042c9e8183cbde0f005a00db1529f,这个提供给客户端测试用。 具体代码如下:

public sealed class ConsumerStore : InMemoryConsumerStore, IConsumerStore { internal static readonly IConsumer FixedConsumer = new OAuthConsumer("2433927322", "87f042c9e8183cbde0f005a00db1529f", "测试应用", ConsumerStatus.Valid); public ConsumerStore() { this.ConsumerDictionary.Add( ConsumerStore.FixedConsumer.Key, ConsumerStore.FixedConsumer); } public override bool Add(IConsumer consumer) { throw new NotSupportedException("Consumers cannot be added to this store--it is fixed."); } public override bool Contains(string consumerKey) { return ConsumerStore.FixedConsumer.Key.Equals(consumerKey); } public override bool Update(IConsumer consumer) { throw new NotSupportedException("Consumers cannot be updated in this store--it is fixed."); } public override bool Remove(IConsumer consumer) { throw new NotSupportedException("Consumers cannot be removed from this store--it is fixed."); } }

四、接下来就是request_token功能,新建RequestTokenHandler.cs ,这个是OAuth.Net.ServiceProvider.RequestTokenHandler子类,并且是httpHandlers所以需要在Web.config中添加httpHandlers配置,这个用来接收客户端程序的请求,返回给客户端程序Request Token和Request Secret用,具体代码如下:
public sealed class RequestTokenHandler : OAuth.Net.ServiceProvider.RequestTokenHandler { protected override void IssueRequestToken(HttpContext httpContext, OAuthRequestContext requestContext) { //产生RequestToken IRequestToken token = this.GenerateRequestToken(httpContext, requestContext); requestContext.RequestToken = token; Uri callbackUri; if (Uri.TryCreate(requestContext.Parameters.Callback, UriKind.Absolute, out callbackUri)) { if (!ServiceProviderContext.CallbackStore.ContainsCallback(token)) { //保存Callback地址了 ServiceProviderContext.CallbackStore.AddCallback(token, callbackUri); } } else OAuthRequestException.ThrowParametersRejected(new string[] { Constants.CallbackParameter }, "Not a valid Uri."); //把token.Token和token.Secret输出到客户端, requestContext.ResponseParameters[Constants.TokenParameter] = token.Token; requestContext.ResponseParameters[Constants.TokenSecretParameter] = token.Secret; } protected override IRequestToken GenerateRequestToken(HttpContext httpContext, OAuthRequestContext requestContext) { return ServiceProviderContext.TokenGenerator.CreateRequestToken(requestContext.Consumer, requestContext.Parameters); } }

五、 接着是authorize功能,新建名为authorize.aspx的页面,用来给用户输入账号和密码进行授权的页面,这个页面很简单具体如下图,在这个页面中获取用户输入的账户和密码跟数据库中存储的用户账号和密码进行验证,如果验证通过返回之前客户端提供的callback地址,并且给这个地址添加一个校验码,具体代码如下:
public partial class authorize : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } protected void Button1_Click(object sender, EventArgs e) { if (loginName.Text == "test" && password.Text == "123") { string toke = Request.Params["oauth_token"]; IRequestToken tk = ServiceProviderContext.TokenStore.GetRequestToken(toke); Uri callback = ServiceProviderContext.CallbackStore.GetCalback(tk); string oauth_verifier = ServiceProviderContext.VerificationProvider.Generate(tk); Response.Redirect(callback.ToString() + "?oauth_verifier=" + oauth_verifier); } } }

六、接下来就是access_token功能,新建AccessTokenHandler.cs , 这个是OAuth.Net.ServiceProvider.AccessTokenHandler子类,并且是httpHandlers所以需要在Web.config中添加httpHandlers配置,这个用来接收客户端程序的请求,返回给客户端程序Access Token和Access Secret用,具体代码如下:
public sealed class AccessTokenHandler : OAuth.Net.ServiceProvider.AccessTokenHandler { protected override void IssueAccessToken(HttpContext httpContext, OAuthRequestContext requestContext) { //产生access token IAccessToken accessToken = this.GenerateAccessToken(httpContext, requestContext); accessToken.Status = TokenStatus.Authorized; // 把accessToken和accessSecret输出到客户端, requestContext.ResponseParameters[Constants.TokenParameter] = accessToken.Token; requestContext.ResponseParameters[Constants.TokenSecretParameter] = accessToken.Secret; } protected override IAccessToken GenerateAccessToken(HttpContext httpContext, OAuthRequestContext requestContext) { return ServiceProviderContext.TokenGenerator.CreateAccessToken(requestContext.Consumer, requestContext.RequestToken); } } public class TokenGenerator : ITokenGenerator { internal static readonly IRequestToken FixedRequestToken = new OAuthRequestToken("requestkey", "requestsecret", ConsumerStore.FixedConsumer, TokenStatus.Authorized, null, ServiceProviderContext.DummyIdentity, new string[] { }); internal static readonly IAccessToken FixedAccessToken = new OAuthAccessToken( "accesskey", "accesssecret", ConsumerStore.FixedConsumer, TokenStatus.Authorized, TokenGenerator.FixedRequestToken); public IRequestToken CreateRequestToken(IConsumer consumer, OAuthParameters parameters) { return TokenGenerator.FixedRequestToken; } public IAccessToken CreateAccessToken(IConsumer consumer, IRequestToken requestToken) { return TokenGenerator.FixedAccessToken; } } public class TokenStore : InMemoryTokenStore, ITokenStore { public TokenStore() { this.RequestTokenDictionary.Add( TokenGenerator.FixedRequestToken.Token, TokenGenerator.FixedRequestToken); this.AccessTokenDictionary.Add( TokenGenerator.FixedAccessToken.Token, TokenGenerator.FixedAccessToken); } public override bool Add(IRequestToken token) { throw new NotSupportedException("Tokens cannot be added to the token store--it is fixed."); } public override bool Add(IAccessToken token) { throw new NotSupportedException("Tokens cannot be added to the token store--it is fixed."); } public override bool Update(IRequestToken token) { throw new NotSupportedException("Tokens cannot be updated in the token store--it is fixed."); } public override bool Update(IAccessToken token) { throw new NotSupportedException("Tokens cannot be updated in the token store--it is fixed."); } public override bool Remove(IRequestToken token) { throw new NotSupportedException("Tokens cannot be removed from the token store--it is fixed."); } public override bool Remove(IAccessToken token) { throw new NotSupportedException("Tokens cannot be removed from the token store--it is fixed."); } }

这样就完成了一个最最简单小型的服务端OAuth认证,然后用android客户端进行测试ok通过。 注意点: 一、android模拟器访问本地服务地址为10.0.2.2,比如http://localhost:3423/authorize.aspx在模拟器中用http://10.0.2.2:3423/authorize.aspx 二、OAuth.Net类库的OAuth.Net.Common项目中的interface ICallbackStore 添加了一个Uri GetCalback(IRequestToken token);并且在具体的实现类InMemoryCallbackStore添加了实习代码: public Uri GetCalback(IRequestToken token) {
lock (this.callbackStore)
{
if (this.callbackStore.ContainsKey(token))
{
return this.callbackStore[token];
}
else
{
return null;
}
}
}



三、为了能用我前面做的给新浪用的android客户端,对于类库源代码AccessTokenHandler的ParseParameters方法做了如下修改,因为新浪请求api的时候都会加一个source的参数: protected virtual void ParseParameters(HttpContext httpContext, OAuthRequestContext requestContext) {
.......
parameters.AllowOnly(
Constants.ConsumerKeyParameter,
Constants.TokenParameter,
Constants.SignatureMethodParameter,
Constants.SignatureParameter,
Constants.TimestampParameter,
Constants.NonceParameter,
Constants.VerifierParameter,
Constants.VersionParameter, // (optional)
Constants.RealmParameter, // (optional)
"source");

......
}


android开发我的新浪微博客户端-大图浏览以及保存篇(7)

6天前 上传 下载附件 (129.72 KB)

6天前 上传 下载附件 (73.8 KB)

在阅读微博的功能篇中,如果微博包含了图片就会在微博正文下面显示该张图片,但是这个图片只是张缩略图,这样就需要提供一个能放大缩小查看这张图片的功能,当点击正文中的缩略图的时候显示一个简单的图片浏览器功能,提供图片的放大、缩小、拖拽操作方便用户查看图片,同时也提供保存图片到手机的功能。本功能的UI比较简单就不单独分篇讲了,具体的实现效果如上图。

新建ImageActivity.java作为图片浏览Activity,在res/layout下新建image.xml的Layout作为图片浏览的布局文件,image.xml布局代码很简单了就不详细解释了直接贴代码:


<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout android:layout_width="fill_parent" android:layout_height="41px" android:background="@drawable/imagebar_bg"> <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="2"> <Button android:id="@+id/returnBtn" android:layout_width="63px" android:layout_height="29px" android:layout_centerInParent="true" android:text="返回" android:textColor="#ffffff" android:background="@drawable/btn1_bg"> </Button> </RelativeLayout> <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="浏览图片" android:textColor="#ffffff"> </TextView> </RelativeLayout> <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="2"> <Button android:id="@+id/downBtn" android:layout_width="60px" android:layout_height="29px" android:layout_centerInParent="true" android:text="下载" android:textColor="#ffffff" android:background="@drawable/btn2_bg"> </Button> </RelativeLayout> </LinearLayout> <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent"> <MySinaWeiBo.ui.ImageZoomView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/pic" android:layout_width="fill_parent" android:layout_height="fill_parent"> </MySinaWeiBo.ui.ImageZoomView> <ZoomControls android:id="@+id/zoomCtrl" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_alignParentBottom="true"> </ZoomControls> </RelativeLayout> </LinearLayout>

上面的代码中用到了一个自定义控件MySinaWeiBo.ui.ImageZoomView,这个就是整个功能的核心部分,用来实现图片的放大、缩小、拖拽的一个图片显示控件,这个控件非我原创,是参考了Android one finger zoom tutorial 这篇博客写出来的,所以我在这里也不贴实现代码了,有兴趣的大家可以直接看看这个文章。 接下要做的就是用这个ImageZoomView来显示图片,在阅读微博内容的页面中当点击内容中的缩略图片的时候会把这个缩略图对应的原图的url传给当前的这个ImageActivity,那么在ImageActivity的onCreate方法中根据这个url获取图片并且设置给ImageZoomView。在onCreate方法中代码如下:
@Override public void onCreate(Bundle savedInstanceState) { 。。。。。 Intent i=this.getIntent(); if(i!=null){ Bundle b=i.getExtras(); if(b!=null){ if(b.containsKey("url")){ String url = b.getString("url"); mZoomView=(ImageZoomView)findViewById(R.id.pic); Drawable img= AsyncImageLoader.loadImageFromUrl(url); image=drawableToBitmap(img); mZoomView.setImage(image); mZoomState = new ZoomState(); mZoomView.setZoomState(mZoomState); mZoomListener = new SimpleZoomListener(); mZoomListener.setZoomState(mZoomState); mZoomView.setOnTouchListener(mZoomListener); resetZoomState(); } } } 。。。。。。 }

private void resetZoomState() { mZoomState.setPanX(0.5f); mZoomState.setPanY(0.5f); final int mWidth = image.getWidth(); final int vWidth= mZoomView.getWidth(); Log.e("iw:",vWidth+""); mZoomState.setZoom(1f); mZoomState.notifyObservers(); } public Bitmap drawableToBitmap(Drawable drawable) { Bitmap bitmap = ((BitmapDrawable)drawable).getBitmap(); return bitmap; }

ZoomControls zoomCtrl = (ZoomControls) findViewById(R.id.zoomCtrl); zoomCtrl.setOnZoomInClickListener(new OnClickListener(){ @Override public void onClick(View view) { float z= mZoomState.getZoom()+0.25f; mZoomState.setZoom(z); mZoomState.notifyObservers(); } }); zoomCtrl.setOnZoomOutClickListener(new OnClickListener(){ @Override public void onClick(View v) { float z= mZoomState.getZoom()-0.25f; mZoomState.setZoom(z); mZoomState.notifyObservers(); } });

这样一个简单的图片浏览器功能就完成了,支持放大缩小并且还能拖拽,基本上达到应用需求。

更多相关文章

  1. Android(安卓)四大组件之---Activity 详解
  2. Android实现手写板和涂鸦功能
  3. Spark实例-每天每个搜索词用户访问
  4. Android用户手势检测
  5. Android(安卓)记住密码功能
  6. Android(安卓)6.0权限使用详解
  7. android中自动提示、补全、连接的功能实现
  8. android通过webservice验证用户
  9. Android开发指南(36) —— Search

随机推荐

  1. 开机提示“Android正在升级...”
  2. Android伸手党系列之八:Android常用开发问
  3. android 的ViewPager的预加载机制及解决
  4. Android实践之简易天气(二)
  5. Android Weak Handler:可以避免内存泄漏的
  6. android 中的广播 ,系统广播和自定义广播
  7. [工作积累] android 中添加libssl和libcu
  8. Android(安卓)中的 framebuffer 和SurFac
  9. Android API Guides---Drawable Resource
  10. Android 中的dm-verity