這是新年第一彈,這幾天家里有些鬧心的事,直到現在還沒解決,所以一直未更新博客。不過這兩個夜晚的時間做了一個QQ登錄界面(2013版),其實我早就想做這么一個界面,只是前一段時間還沒有復習太多的東西,現在在處理那些煩心事的零散時間做出來了基本的界面,也算是值得高興的事情吧。
話不多說,這次因為可能講的內容比較多,可能會分兩次講,所以首先先上圖,以便對最終實現能夠達到什么養的效果心中有數。
這是手機QQ2013官方版的登錄界面:

這個是我自己做出來的 QQ登錄界面:

當然與官方版相比還是有很大的差距,不過對於學習安卓控件的使用已經足夠了。
為實現上述界面,需要有幾個關鍵的知識點需要學習:
一、實現圓角的效果——學會使用描述背景的drawable/中的 xml文件
需要在drawable文件夾中創建xml文件,文件的父控件類型為shape,在shape父控件中,有<solid/> <corners/> <stroke/> <padding/> 等屬性,分別處理背景的填充顏色、邊角的曲率、邊框線的寬度和顏色、上下左右內邊框(即背景超出使用改背景的空間的寬度)
例如,若想實現一個圓角的ImageButton,可以創建一個 fillet_shape.xml文件
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" > <solid android:color="#ffffff"/> <corners android:radius="10px"/> <padding android:left="3dip" android:top="3dip" android:right="3dip" android:bottom="3dip"/> </shape>
然后在Activity類中用ImageButton的實例設置setBackgroundResource(); 或者在xml布局文件中在配置控件屬性使用 android:background="@drawable/fillet_shape"
注意這里在配置好背景之后,在為ImageView設置顯示的圖片時,只能使用setImageResource()而不能使用setBackgroundResource();
學會這點很重要,后面就可以舉一反三,本例中使用該方法為 EditText設置了邊框,為ListView的每一個Item設置了邊框,為按鈕設置了圓角背景。就不再特殊說明
二、RelativeLayout中控件的布局問題
不同控件之間是可以覆蓋的,注意在布局文件中后配置的空間可以覆蓋掉之前配置的空間,所以本例在布局文件中讓ListView控件放在了最后,另外如果想要一個控件暫時消失
可以使用setVisibility(View.GONE);的方法,這樣改控件消失以后被覆蓋的空間就可以正常使用了。另外本例在EditText中添加按鈕並沒有自定義EditText,而是直接通過布局文件的描述將聯系人游標(小箭頭)嵌在了Edittext中。注意這里一般不使用View.INVISIBLE,這樣控件並未消失
三、notifyDataSetChanged方法是BaseAdapter的方法,所以可以在構造的適配器內部或者創建的適配器對象使用。
四、並不是只有Button可以設置OnClickListener 實際上很多常見的空間都可以使用,如EditText或者TextView ,這個應該是屬於View的抽象方法
五、ListView如何調整每一個Item邊框的寬度並且避免Item之間的分割線顏色太深?
方法就是上面介紹的自定義drawable/ 中xml文件,來配置邊和背景屬性,另外在配置ListView控件的屬性時 設置android:divider="#aaaaaa" android:dividerHeight="0px" 這樣可以是ListItem的邊框做出上圖所示的效果。
六、怎樣解決ListView中添加Button之后就不響應單擊事件的問題?
原因是Button搶奪了焦點,最簡單的解決辦法是:在自定義的每一個ListItem的布局文件中在根標簽的屬性中添加上 android:descendantFocusability="blocksDescendants" 即拒絕ListItem中的子控件獲得焦點
七、怎樣實現在點擊某個控件以外的屏幕區域就使該控件消失的效果?本例中實現在點擊ListView以外的區域就會使ListView消失的效果。
方法是覆寫MainActivity的onTouchEvent()方法,根據點擊的坐標(x,y)與目標控件通過getLocation獲得的控件左上角坐標,再結合目標控件的寬和高,判斷點擊的點是否在控件內,進而決定對該控件執行怎樣的操作。
例子:
@Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub if(event.getAction()==MotionEvent.ACTION_DOWN && isVisible){ int[] location=new int[2]; //調用getLocationInWindow方法獲得某一控件在窗口中左上角的橫縱坐標 loginList.getLocationInWindow(location); //獲得在屏幕上點擊的點的坐標 int x=(int)event.getX(); int y=(int)event.getY(); if(x<location[0]|| x>location[0]+loginList.getWidth() || y<location[1]||y>location[1]+loginList.getHeight()){ isIndicatorUp=false; isVisible=false; listIndicatorButton.setBackgroundResource(R.drawable.indicator_down); loginList.setVisibility(View.GONE); //讓ListView列表消失,並且讓游標向下指! } } return super.onTouchEvent(event); }
以上就是我在寫程序的過程中遇到的一些難題,天有些晚了,直接上所有的代碼吧。。
首先布局文件activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" android:background="#dde1ee" > <ImageView android:id="@+id/myImage" android:layout_width="70dip" android:layout_height="70dip" android:layout_marginTop="65dip" android:layout_centerHorizontal="true" android:background="@drawable/fillet_shape"/> <EditText android:id="@+id/qqNum" android:layout_width="match_parent" android:layout_height="40dip" android:layout_marginLeft="30dip" android:layout_marginRight="30dip" android:layout_marginTop="15dip" android:paddingLeft="50dip" android:layout_below="@id/myImage" android:inputType="number" android:background="@drawable/qqnum_edit"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="賬號" android:textSize="8pt" android:textColor="@android:color/darker_gray" android:layout_alignLeft="@id/qqNum" android:layout_alignTop="@id/qqNum" android:layout_marginTop="9dip" android:layout_marginLeft="3dip"/> <EditText android:id="@+id/qqPassword" android:layout_width="match_parent" android:layout_height="40dip" android:paddingLeft="50dip" android:layout_marginLeft="30dip" android:layout_marginRight="30dip" android:layout_below="@id/qqNum" android:background="@drawable/qqnum_edit"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="密碼" android:textSize="8pt" android:textColor="@android:color/darker_gray" android:layout_alignLeft="@id/qqPassword" android:layout_alignTop="@id/qqPassword" android:layout_marginTop="9dip" android:layout_marginLeft="3dip"/> <ImageButton android:id="@+id/qqListIndicator" android:layout_width="22dip" android:layout_height="20dip" android:layout_marginBottom="8dip" android:layout_marginRight="3dip" android:layout_alignBottom="@+id/qqNum" android:layout_alignRight="@+id/qqNum" android:background="@drawable/indicator_down" /> <ImageButton android:id="@+id/delete_button_edit" android:layout_width="18dip" android:layout_height="18dip" android:layout_marginBottom="8dip" android:layout_marginRight="3dip" android:layout_alignBottom="@+id/qqNum" android:layout_toLeftOf="@id/qqListIndicator" android:background="@drawable/delete_button_edit" android:visibility="gone"/> <Button android:id="@+id/qqLoginButton" android:layout_width="match_parent" android:layout_height="35dip" android:layout_below="@id/qqPassword" android:layout_alignLeft="@id/qqNum" android:layout_alignRight="@id/qqNum" android:layout_marginTop="20dip" android:background="@drawable/login_button_back" android:text="登錄" android:textColor="@android:color/white"/> <TextView android:id="@+id/fetchPassword" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" android:layout_below="@id/qqLoginButton" android:layout_marginLeft="45dip" android:text="找回密碼" android:textSize="7pt" android:textColor="#333355" android:gravity="bottom"/> <TextView android:id="@+id/registQQ" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/qqLoginButton" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:layout_marginRight="45dip" android:layout_marginTop="5dip" android:text="注冊賬號" android:textSize="7pt" android:textColor="#333355" android:gravity="bottom"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/qqLoginButton" android:text="|" android:textSize="7pt" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:gravity="bottom"/> <ListView android:id="@+id/loginQQList" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_above="@id/registQQ" android:layout_alignLeft="@id/qqNum" android:layout_alignRight="@id/qqNum" android:layout_below="@id/qqNum" android:focusable="true" android:focusableInTouchMode="true" android:visibility="gone" android:divider="#aaaaaa" android:dividerHeight="0px"/> </RelativeLayout>
listItem的布局文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="35dip" android:orientation="horizontal" android:background="@drawable/list_item_border" android:descendantFocusability="blocksDescendants" > <ImageView android:id="@+id/login_userPhoto" android:layout_width="match_parent" android:layout_weight="4.7" android:layout_height="30dip" android:background="@drawable/contact_1" android:layout_marginLeft="10dip" android:layout_marginTop="5dip" android:layout_marginRight="15dip" android:layout_marginBottom="5dip"/> <TextView android:id="@+id/login_userQQ" android:layout_width="match_parent" android:layout_height="30dip" android:layout_marginTop="5dip" android:layout_weight="2" android:gravity="center_vertical" android:text="1234567890" android:textSize="7pt" /> <ImageButton android:id="@+id/login_deleteButton" android:layout_width="match_parent" android:layout_weight="5.8" android:layout_height="14dip" android:layout_marginTop="11dip" android:layout_marginRight="3dip" android:focusable="true" android:focusableInTouchMode="true" android:background="@drawable/deletebutton" /> </LinearLayout>
至於實現圓角等邊框效果的xml布局文件就不再添加,上面第一條已經給出例子,可以根據需要的效果進行推廣。
然后就是MainActivity文件,這里為了適配器類等夠對Activity界面進行更改,將適配器類寫成了MainActivity類的內部類,代碼中有說明。另外相當一部分代碼是為了實現一些細節性的東西,如EditText中游標的的方向變化,圖片圖案的變化,使用了一些常量。
package com.example.android_qq_login; import java.util.ArrayList; import java.util.HashMap; import com.qqlist.contactor.UserInfo; import android.os.Bundle; import android.app.Activity; import android.content.Context; import android.view.LayoutInflater; import android.view.Menu; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.View.OnClickListener; import android.widget.*; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemSelectedListener; public class MainActivity extends Activity { TextView textFetchPassWord=null,textRegister=null; Button loginButton=null; ImageButton listIndicatorButton=null, deleteButtonOfEdit=null; ImageView currentUserImage=null; ListView loginList=null; EditText qqEdit=null, passwordEdit=null; private static boolean isVisible=false; //ListView是否可見 private static boolean isIndicatorUp=false; //指示器的方向 public static int currentSelectedPosition=-1; //用於記錄當前選擇的ListView中的QQ聯系人條目的ID,如果是-1表示沒有選擇任何QQ賬戶,注意在向 //List中添加條目或者刪除條目時都要實時更新該currentSelectedPosition String[] from={"userPhoto","userQQ","deletButton"}; int[] to={R.id.login_userPhoto,R.id.login_userQQ,R.id.login_deleteButton}; ArrayList<HashMap<String,Object>> list=null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textFetchPassWord=(TextView)findViewById(R.id.fetchPassword); textRegister=(TextView)findViewById(R.id.registQQ); loginButton=(Button)findViewById(R.id.qqLoginButton); listIndicatorButton=(ImageButton)findViewById(R.id.qqListIndicator); loginList=(ListView)findViewById(R.id.loginQQList); list=new ArrayList<HashMap<String,Object>>(); currentUserImage=(ImageView)findViewById(R.id.myImage); qqEdit=(EditText)findViewById(R.id.qqNum); passwordEdit=(EditText)findViewById(R.id.qqPassword); deleteButtonOfEdit=(ImageButton)findViewById(R.id.delete_button_edit); qqEdit.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub if(qqEdit.getText().toString().equals("")==false){ deleteButtonOfEdit.setVisibility(View.VISIBLE); } } }); deleteButtonOfEdit.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub currentUserImage.setImageResource(R.drawable.qqmain); qqEdit.setText(""); currentSelectedPosition=-1; deleteButtonOfEdit.setVisibility(View.GONE); } }); UserInfo user1=new UserInfo(R.drawable.contact_0,"1234567",R.drawable.deletebutton); UserInfo user2=new UserInfo(R.drawable.contact_1,"10023455",R.drawable.deletebutton); addUser(user1); addUser(user2); //設置當前顯示的被選中的賬戶的頭像 if(currentSelectedPosition==-1){ currentUserImage.setImageResource(R.drawable.qqmain); qqEdit.setText(""); } else{ currentUserImage.setImageResource((Integer)list.get(currentSelectedPosition).get(from[0])); qqEdit.setText((String)list.get(currentSelectedPosition).get(from[1])); } MyLoginListAdapter adapter=new MyLoginListAdapter(this, list, R.layout.layout_list_item, from, to); loginList.setAdapter(adapter); loginList.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) { // TODO Auto-generated method stub currentUserImage.setImageResource((Integer)list.get(arg2).get(from[0])); qqEdit.setText((String)list.get(arg2).get(from[1])); currentSelectedPosition=arg2; //相應完點擊后List就消失,指示箭頭反向! loginList.setVisibility(View.GONE); listIndicatorButton.setBackgroundResource(R.drawable.indicator_down); System.out.println("---------Selected!!"); } }); listIndicatorButton.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub if(isIndicatorUp){ isIndicatorUp=false; isVisible=false; listIndicatorButton.setBackgroundResource(R.drawable.indicator_down); loginList.setVisibility(View.GONE); //讓ListView列表消失 } else{ isIndicatorUp=true; isVisible=true; listIndicatorButton.setBackgroundResource(R.drawable.indicator_up); loginList.setVisibility(View.VISIBLE); } } }); } //繼承onTouchEvent方法,用於實現點擊控件之外的部分使控件消失的功能 private void addUser(UserInfo user){ HashMap<String,Object> map=new HashMap<String,Object>(); map.put(from[0], user.userPhoto); map.put(from[1], user.userQQ); map.put(from[2], user.deleteButtonRes); list.add(map); } @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub if(event.getAction()==MotionEvent.ACTION_DOWN && isVisible){ int[] location=new int[2]; //調用getLocationInWindow方法獲得某一控件在窗口中左上角的橫縱坐標 loginList.getLocationInWindow(location); //獲得在屏幕上點擊的點的坐標 int x=(int)event.getX(); int y=(int)event.getY(); if(x<location[0]|| x>location[0]+loginList.getWidth() || y<location[1]||y>location[1]+loginList.getHeight()){ isIndicatorUp=false; isVisible=false; listIndicatorButton.setBackgroundResource(R.drawable.indicator_down); loginList.setVisibility(View.GONE); //讓ListView列表消失,並且讓游標向下指! } } return super.onTouchEvent(event); } /** * 為了便於在適配器中修改登錄界面的Activity,這里把適配器作為 * MainActivity的內部類,避免了使用Handler,簡化代碼 * @author DragonGN * */ public class MyLoginListAdapter extends BaseAdapter{ protected Context context; protected ArrayList<HashMap<String,Object>> list; protected int itemLayout; protected String[] from; protected int[] to; public MyLoginListAdapter(Context context, ArrayList<HashMap<String, Object>> list, int itemLayout, String[] from, int[] to) { super(); this.context = context; this.list = list; this.itemLayout = itemLayout; this.from = from; this.to = to; } @Override public int getCount() { // TODO Auto-generated method stub return list.size(); } @Override public Object getItem(int arg0) { // TODO Auto-generated method stub return null; } @Override public long getItemId(int position) { // TODO Auto-generated method stub return position; } class ViewHolder{ public ImageView userPhoto; public TextView userQQ; public ImageButton deleteButton; } @Override public View getView(int position, View convertView, ViewGroup parent) { // TODO Auto-generated method stub ViewHolder holder=null; /* currentPosition=position; 不能使用currentPosition,因為每繪制完一個Item就會更新currentPosition 這樣得到的currentPosition將始終是最后一個Item的position */ if(convertView==null){ convertView=LayoutInflater.from(context).inflate(itemLayout, null); holder=new ViewHolder(); holder.userPhoto=(ImageView)convertView.findViewById(to[0]); holder.userQQ=(TextView)convertView.findViewById(to[1]); holder.deleteButton=(ImageButton)convertView.findViewById(to[2]); convertView.setTag(holder); } else{ holder=(ViewHolder)convertView.getTag(); } holder.userPhoto.setBackgroundResource((Integer)list.get(position).get(from[0])); holder.userQQ.setText((String)list.get(position).get(from[1])); holder.deleteButton.setBackgroundResource((Integer)list.get(position).get(from[2])); holder.deleteButton.setOnClickListener(new ListOnClickListener(position)); return convertView; } class ListOnClickListener implements OnClickListener{ private int position; public ListOnClickListener(int position) { super(); this.position = position; } @Override public void onClick(View arg0) { // TODO Auto-generated method stub list.remove(position); //如果刪除的就是當前顯示的賬號,那么將主界面當前顯示的頭像設置回初始頭像 if(position==currentSelectedPosition){ currentUserImage.setImageResource(R.drawable.qqmain); qqEdit.setText(""); currentSelectedPosition=-1; } else if(position<currentSelectedPosition){ currentSelectedPosition--; //這里小於當前選擇的position時需要進行減1操作 } listIndicatorButton.setBackgroundResource(R.drawable.indicator_down); loginList.setVisibility(View.GONE); //讓ListView列表消失,並且讓游標向下指! MyLoginListAdapter.this.notifyDataSetChanged(); } } } }
另外再多附幾張效果圖:

大概就這些了,趕緊去休息。
