蘑菇街作為中國最大女性購物社群,其app的設計水準也毋庸置疑的,最近部落格将連續來仿造一個蘑菇街的app的界面設計。
(1)準備工作
在閱讀郭霖大神的部落格時有人問裡面使用的美工素材怎麼得到的,其實很簡單,下載下傳一個app,把apk格式修改成rar後解壓,你會在目錄下看到所有的素材。
随後,看看app的界面:
第一個是啟動界面,第二個是主界面,先來看第一個界面。
(1)啟動界面(splash)。
啟動界面也叫splash界面,是app啟動時的第一畫面,主要用于介紹應用、宣傳或者加載資料,或者兼而有之。這裡之是以要單獨拿出來是因為這個應用有個獨特的,它的logo是透明漸變出現的,也就是淡入效果,我們知道android主要有四種動畫:透明動畫、縮放、位移、旋轉,分别使用的alphaanimation、scaleanimation、translateanimation、rotateanimation,這裡我們使用alphaanimation。
界面布局很簡單,如activity_loading.xml:
[html] view
plaincopy
<?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="match_parent"
android:orientation="vertical"
android:gravity="center"
android:background="@drawable/init_bg">
<imageview
android:id="@+id/logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/init_logo"/>
</linearlayout>
建立一個loadingactivity類:如下代碼:
[java] view
public class loadingactivity extends activity{
@override
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
//設定全屏
this.requestwindowfeature(window.feature_no_title);
this.getwindow().setflags(windowmanager.layoutparams.flag_fullscreen, windowmanager.layoutparams.flag_fullscreen);
setcontentview(r.layout.activity_loading);
imageview view =(imageview) findviewbyid(r.id.logo);
alphaanimation aa = new alphaanimation(0.01f,1.0f);//透明度變化
aa.setduration(1500);//設定漸變時間
view.startanimation(aa);//設定漸變的view
aa.setanimationlistener(new animationlistener(){
//動畫結束後自動執行
@override
public void onanimationend(animation arg0) {
redirectto();
}
public void onanimationrepeat(animation animation) {
public void onanimationstart(animation animation) {
});
}
/**
* 跳轉到登陸界面
*/
private void redirectto(){
intent intent = new intent(this, mainactivity.class);
startactivity(intent);
finish();
}
注意2個地方:a. alphaanimation aa = new alphaanimation(0.01f,1.0f); 構造方法:alphaanimation(float fromalpha, float toalpha),表示從透明度0.01到1.0的漸變,我們知道0.0表示全透明,1.0表示完全不透明.
b. onanimationend方法,顧名思義,表示:在動畫結束後自動執行這個方法,這裡當然是跳轉到主界面了。運作後其效果如下:
是不是很簡單,以後做啟動畫面都可以采用類似的方法。
總結要點:alphaanimation類
(1)主界面。
看看原圖,如下:
中間的内容先不管,我們看actionbar和底部菜單,上面是一個自定義的actionbar,下面是一個切換菜單,而且下面菜單在改變的時候上面的actionbar也在變。我們這裡采用的設計方法是fragment+activity混合使用,底部菜單使用radiobutton,中間留出一個活動的fragment
先來看actionbar,觀察發現actionbar分為2類,第一個是圖檔标題,另外一個是文字标題。
是以自定義的actionbar需要2個xml布局檔案,分别命名為:actionbar_index.xml和actionbar_usu.xml
其xml布局分别為:actionbar_index.xml
<framelayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent"
android:id="@+id/index_action_layout"
android:background="@drawable/title_bg">
<imagebutton
android:id="@+id/btn_slidemenu"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:background="@android:color/transparent"
android:clickable="true"
android:paddingleft="8dip"
android:src="@drawable/index_logo" />
<imagebutton android:id="@+id/btn_main_qrcode"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:src="@drawable/icon_qc"
android:paddingright="8dip"
android:clickable="true"
android:layout_gravity="right|center_vertical"/>
</framelayout>
actionbar_usu.xml
<textview
android:id="@+id/tv_title"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:textcolor="@color/white1"
android:textsize="@dimen/actionbar_title"
android:background="@android:color/transparent"
android:clickable="true"
android:paddingleft="8dip"
/>
這個不多講,唯一值得注意的是需要使用framelayout來布局,其中一些strings 、colors以及dimens是自己定義的最後我會将代碼傳上去。
建立一個actionbartool工具類,主要用來設定每個界面的actionbar,代碼如下:
public class actionbartool {
activity activity;
public actionbartool(activity activity){
this.activity=activity;
* 設定主界面的index
public void setindexactionbar(){
actionbar actionbar=activity.getactionbar();
actionbar.layoutparams params=new actionbar.layoutparams(layoutparams.match_parent,layoutparams.match_parent,gravity.center );
view view=layoutinflater.from(activity).inflate(r.layout.actionbar_index, null);
actionbar.setcustomview(view,params);
actionbar.setdisplayoptions(actionbar.display_show_custom);
actionbar.setdisplayshowcustomenabled(true);
* 設定其他界面的actionbar
* @param title 标題
public void setusuactionbar(string title){
view view=layoutinflater.from(activity).inflate(r.layout.actionbar_usu, null);
textview tv_title=(textview)activity.findviewbyid(r.id.tv_title);
tv_title.settext(title);
這是自定義目錄的一般做法,但是安卓的設計文檔裡面是不推薦這麼使用的,因為這樣會破壞actionbar的一些靈活性,而且到後期很難管理,這裡之是以使用是因為文字居中需要自己定義視圖,其實可以直接用背景來代替,這樣也能實作文字居中,而且不會破壞actonbar的靈活性。
再來看看底部菜單:main.xml檔案如下
<?xml version="1.0" encoding="utf-8"?><linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:background="#fff"
android:orientation="vertical" >
<framelayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_weight="1"
android:background="#fff"/>
<radiogroup
android:background="@color/gray"
android:layout_margintop="2dp"
android:layout_weight="10"
android:orientation="horizontal" >
<radiobutton
android:id="@+id/fragment_index"
android:button="@null"
android:drawabletop="@drawable/index_indicator"
android:text="@string/index"
android:textcolor="@color/white"
android:textsize="12sp" />
android:id="@+id/fragment_category"
android:layout_height="wrap_content"
android:drawabletop="@drawable/category_indicator"
android:text="@string/category"
android:id="@+id/fragment_discovery"
android:drawabletop="@drawable/discovery"
android:text="@string/discovery"
android:id="@+id/fragment_cart"
android:drawablepadding="3dip"
android:drawabletop="@drawable/cart_tab_icon_dark"
android:text="@string/cart"
android:id="@+id/fragment_my"
android:layout_height="wrap_content"
android:drawabletop="@drawable/my_indicator"
android:text="@string/my"
</radiogroup>
radiogroup +活動的framelayout 構成了整個頁面的布局。
接下來是mian 頁面的切換代碼,mainactivity.java
public class mainactivity extends fragmentactivity {
radiobutton btn_index, btn_category, btn_discovery, btn_cart, btn_my;
private fragment fragment;
fragmentmanager fragmentmanager;
actionbartool actionbartool;
super.oncreate(savedinstancestate);
setcontentview(r.layout.main);
initui();
* 初始化ui界面
private void initui() {
actionbartool=new actionbartool(this);
btn_index = (radiobutton) findviewbyid(r.id.fragment_index);
btn_category = (radiobutton) findviewbyid(r.id.fragment_category);
btn_discovery = (radiobutton) findviewbyid(r.id.fragment_discovery);
btn_cart = (radiobutton) findviewbyid(r.id.fragment_cart);
btn_my = (radiobutton) findviewbyid(r.id.fragment_my);
btn_my.setonclicklistener(new switchfragment());
btn_cart.setonclicklistener(new switchfragment());
btn_discovery.setonclicklistener(new switchfragment());
btn_category.setonclicklistener(new switchfragment());
btn_index.setonclicklistener(new switchfragment());
fragment = new indexfragment();
actionbartool.setindexactionbar();
btn_index.settextcolor(getresources().getcolor(r.color.red));
btn_index.setcompounddrawableswithintrinsicbounds(null,
getresources().getdrawable(r.drawable.index_indicator_s), null, null);
fragmentmanager =getsupportfragmentmanager();
if (fragment != null) {
fragmentmanager.begintransaction()
.replace(r.id.content, fragment).commit();
} else {
log.e("mainactivity", "error in creating fragment");
}
* 切換fragment
* @author zw.yan
*
class switchfragment implements view.onclicklistener {
@override
public void onclick(view arg0) {
initbg();//每一次都初始化按鈕樣式
switch (arg0.getid()) {
case r.id.fragment_index:
fragment = new indexfragment();
actionbartool.setindexactionbar();
btn_index.settextcolor(getresources().getcolor(r.color.red));
btn_index.setcompounddrawableswithintrinsicbounds(null,
getresources().getdrawable(r.drawable.index_indicator_s), null, null);
break;
case r.id.fragment_category:
fragment = new categoryfragment();
actionbartool.setusuactionbar("分類");
btn_category.settextcolor(getresources().getcolor(r.color.red));
btn_category.setcompounddrawableswithintrinsicbounds(null,
getresources().getdrawable(r.drawable.category_indicator_s), null, null);
case r.id.fragment_discovery:
fragment = new discoveryfragment();
actionbartool.setusuactionbar("發現");
btn_discovery.settextcolor(getresources().getcolor(r.color.red));
btn_discovery.setcompounddrawableswithintrinsicbounds(null,
getresources().getdrawable(r.drawable.discovery_s), null, null);
case r.id.fragment_cart:
fragment = new cartfragment();
actionbartool.setusuactionbar("我的購物車");
btn_cart.settextcolor(getresources().getcolor(r.color.red));
btn_cart.setcompounddrawableswithintrinsicbounds(null,
getresources().getdrawable(r.drawable.cart_tab_icon_red), null, null);
case r.id.fragment_my:
fragment = new myfragment();
//actionbartool.setusuactionbar("我的");
btn_my.settextcolor(getresources().getcolor(r.color.red));
btn_my.setcompounddrawableswithintrinsicbounds(null,
getresources().getdrawable(r.drawable.my_indicator_s), null, null);
if (fragment != null) {
fragmentmanager.begintransaction()
.replace(r.id.content, fragment).commit();
} else {
log.e("mainactivity", "error in creating fragment");
* 初始化按鈕樣式
private void initbg(){
btn_index.settextcolor(getresources().getcolor(r.color.white));
getresources().getdrawable(r.drawable.index_indicator), null, null);
btn_category.settextcolor(getresources().getcolor(r.color.white));
btn_category.setcompounddrawableswithintrinsicbounds(null,
getresources().getdrawable(r.drawable.category_indicator), null, null);
btn_cart.settextcolor(getresources().getcolor(r.color.white));
btn_cart.setcompounddrawableswithintrinsicbounds(null,
getresources().getdrawable(r.drawable.cart_tab_icon_dark), null, null);
btn_discovery.settextcolor(getresources().getcolor(r.color.white));
btn_discovery.setcompounddrawableswithintrinsicbounds(null,
getresources().getdrawable(r.drawable.discovery), null, null);
btn_my.settextcolor(getresources().getcolor(r.color.white));
btn_my.setcompounddrawableswithintrinsicbounds(null,
getresources().getdrawable(r.drawable.my_indicator), null, null);
public boolean oncreateoptionsmenu(menu menu) {
return true;
其他頁面的fragment.類似,布局檔案是空布局:
public class indexfragment extends fragment{
public void onactivitycreated(bundle savedinstancestate) {
// todo auto-generated method stub
super.onactivitycreated(savedinstancestate);
public view oncreateview(layoutinflater inflater, viewgroup container,
bundle savedinstancestate) {
return inflater.inflate(r.layout.fragment_index, container, false);
上面的主要代碼是:switchfragment類,是切換fragment的,通過fragmentmanager.begintransaction().replace(r.id.content, fragment).commit();來替換目前的fragment,在替換的時候需要先使用initbg()初始化按鈕的顔色,在來改變按鈕的字型顔色和圖示。效果如下:
今天我們來重點來仿照一下第一個底部菜單“愛逛”,首先我們來分解一下功能區域:
1.功能區域分解
(1) pagetabs左右切換菜單:這裡我們使用第三方開源插件,不過需要自己進行修改,采用的是viewpage進行頁面的切換,左右滑動。
(2) 圖檔輪播:viewgroup+viewpage每一個viewgroup存儲一個按壓效果的dot和一張圖檔,随着手指的滑動進行圖檔之間的切換,當然我們這裡隻使用五張圖檔。
(3)更新時間:這個不說了,就是設定時間,這裡找不到圖檔我自己設定固定值,當然也可以通過代碼設定。
(4)第一個清單,第二個清單,我們觀察布局可知,下面2個布局的大小是分别占用了螢幕的一般,我們可以通過設定權值屬性來設定大小,也就是
<span style="white-space:pre"> </span>android:layout_width="match_parent"
android:layout_weight="2"
第一個布局是線性布局的,線性布局裡面包括一個更新時間的一些textview控件和一個gridview控件,第二個布局是一個單獨的gridview控件
2.實作方式
(1) pagetabs:
第一個難點毋庸置疑就是pagetabs菜單,所幸這方面的開源元件挺多,我們可以使用郭霖大神推薦的pagerslidingtabstrip,當然也要進行修改,修改包括橫條的顔色。每一個pagetabs就是一個fragment,因為放置這個pagetabs的本身就是一個fragment,是以我們需要注意,在使用fragmentmanager()的地方,必須要使用目前fragment的子fragmentmanager,否則會報錯。
android:background="#eeeeee">
<com.blog.mogujie.tool.pagerslidingtabstrip
android:id="@+id/tabs"
android:layout_height="40dp" />
<android.support.v4.view.viewpager
android:id="@+id/pager"
android:layout_height="wrap_content" />
pagerslidingtabstrip為自定義控件,也就是第三方插件,viewpager用來顯示每一個頁面的大小,但是注意到修改滾動條的長度,修改代碼如下:
@override
protected void ondraw(canvas canvas) {
super.ondraw(canvas);
if (isineditmode() || tabcount == 0) {
return;
final int height = getheight();
// draw underline
rectpaint.setcolor(underlinecolor);
canvas.drawrect(0, height - underlineheight, tabscontainer.getwidth(), height, rectpaint);
// draw indicator line
rectpaint.setcolor(indicatorcolor);
// default: line below current tab
view currenttab = tabscontainer.getchildat(currentposition);
float lineleft = currenttab.getleft();
float lineright = currenttab.getright();
// if there is an offset, start interpolating left and right coordinates between current and next tab
if (currentpositionoffset > 0f && currentposition < tabcount - 1) {
view nexttab = tabscontainer.getchildat(currentposition + 1);
final float nexttableft = nexttab.getleft();
final float nexttabright = nexttab.getright();
lineleft = (currentpositionoffset * nexttableft + (1f - currentpositionoffset) * lineleft);
lineright = (currentpositionoffset * nexttabright + (1f - currentpositionoffset) * lineright);
canvas.drawrect(lineleft+100, height - indicatorheight, lineright-100, height, rectpaint);
// draw divider
dividerpaint.setcolor(dividercolor);
for (int i = 0; i < tabcount - 1; i++) {
view tab = tabscontainer.getchildat(i);
canvas.drawline(tab.getright(), dividerpadding, tab.getright(), height - dividerpadding, dividerpaint);
主要看第這一行
canvas.drawrect(lineleft+100, height - indicatorheight, lineright-100, height, rectpaint);
這行lineleft+100,lineright-100設定左右兩邊同時減小100的長度。
設定indexfragment代碼,該代碼就是“精選”菜單區域fragment,代碼如下:
[cpp] view
public class indexfragment extends fragment {
private pagerslidingtabstrip tabs;
private displaymetrics dm;
private string[] titles = { "精選", "搭配", "團購" };
private viewpager pager;
dm = getresources().getdisplaymetrics();
pager = (viewpager) getview().findviewbyid(r.id.pager);
tabs = (pagerslidingtabstrip) getview().findviewbyid(r.id.tabs);
//因為這裡是嵌套使用fragment,是以這裡不能直接傳getactivity().getsupportfragmentmanager(),他傳回的是父fragment
//應當使用目前fragment的fragmentmanager(),傳回目前fragment
pager.setadapter(new pageradapter(this.getchildfragmentmanager()));
tabs.setviewpager(pager);
inittabsconfig();
private void inittabsconfig() {
// 設定tab是自動填充滿螢幕的
tabs.setshouldexpand(true);
// 設定tab的分割線是透明的
tabs.setdividercolor(color.transparent);
// 設定tab底部線的高度
tabs.setunderlineheight((int) typedvalue.applydimension(
typedvalue.complex_unit_dip, 1, dm));
// 設定tab indicator的高度
tabs.setindicatorheight((int) typedvalue.applydimension(
typedvalue.complex_unit_dip, 2, dm));
// 設定tab标題文字的大小
tabs.settextsize((int) typedvalue.applydimension(
typedvalue.complex_unit_sp, 16, dm));
// 設定tab indicator的顔色
tabs.setindicatorcolor(getresources().getcolor(r.color.red));
// 設定選中tab文字的顔色
tabs.setselectedtextcolor(getresources().getcolor(r.color.red));
// 取消點選tab時的背景色
tabs.settabbackground(0);
* 此處應當繼承fragmentstatepageradapter
* 在處理資料量較大的頁面應當使用fragmentstatepageradapter,而不是fragmentpageradapter
public class pageradapter extends fragmentstatepageradapter {
public pageradapter(fragmentmanager fm) {
super(fm);
public charsequence getpagetitle(int position) {
return titles[position];
public int getcount() {
return titles.length;
@override
public fragment getitem(int position) {
switch (position) {
case 0:
fragment = new goodsfargment();
break;
case 1:
fragment = new shopingsfragment();
case 2:
fragment = new matchfragment();
default:
return fragment;
注意到代碼,這裡使用this.getchildfragmentmanager()來表示目前的fragment為子fragment,不能使用getactivity().getsupportfragmentmanager(),否則在切換時候會出錯,第二個地方為第x行,這裡繼承的是 fragmentstatepageradapte,而非fragmentpageradapter。
(2)圖檔輪播
我們知道ontouch事件的響應機制是逐級響應的,他會自動響應最底層的view,是以考慮到圖檔輪播需要左右滑動,而pagetabs也會左右滑動,并且pagetabs在圖檔輪轉view的下一層,如果使用原生控件,系統會優先響應pagetabs而不會響應viewpage的圖檔滑動;是以需要考慮,重寫viewpager控件,讓該控件隻會響應自己的左右滑動事件,其父視圖的view左右滑動事件不響應。
public class childviewpager extends viewpager {
public childviewpager(context context, attributeset attrs) {
super(context, attrs);
// todo auto-generated constructor stub
public childviewpager(context context) {
super(context);
public boolean onintercepttouchevent(motionevent arg0) {
// 當攔截觸摸事件到達此位置的時候,傳回true,
// 說明将ontouch攔截在此控件,進而執行此控件的ontouchevent
public boolean ontouchevent(motionevent arg0) {
getparent().requestdisallowintercepttouchevent(true);
return super.ontouchevent(arg0);
代碼很少,主要是這一句getparent().requestdisallowintercepttouchevent(true);
設定父控件不響應ontouch事件,而是交給目前控件的ontouchevent事件,進而阻止pagetabs的滑動,響應目前控件的滑動事件
最後這是viewpage的資料擴充卡,添加圖檔和點,進行左右的移動,定義imgaepageradapter擴充卡類,其代碼如下:
public class imgaepageradapter extends pageradapter {
imageview[] mimageviews;
public imgaepageradapter(imageview[] mimageviews) {
this.mimageviews = mimageviews;
//擷取要滑動的控件的數量
public int getcount() {
return integer.max_value;
//來判斷顯示的是否是同一張圖檔,這裡我們将兩個參數相比較傳回即可
public boolean isviewfromobject(view arg0, object arg1) {
return arg0 == arg1;
//pageradapter如果滑動的圖檔超出了緩存的範圍,就會調用這個方法,将圖檔銷毀
public void destroyitem(view container, int position, object object) {
((viewpager) container).removeview(mimageviews[position
% mimageviews.length]);
*循環讀取圖檔,取餘數
public object instantiateitem(view container, int position) {
((viewpager) container).addview(mimageviews[position
% mimageviews.length], 0);
return mimageviews[position % mimageviews.length];
循環圖檔的代碼主要放在了instantiateitem中,該事件負責将圖檔添加到容器中,并傳回該圖檔視圖,并且每次傳回的圖檔為目前的位置和圖檔的總長度取餘數,通過取餘數進而判斷是否進行循環。
在fragment中調用如下的代碼對資料擴充卡的綁定
viewpager.setadapter(new imgaepageradapter(mimageviews));
viewpager.setonpagechangelistener(this);
(3)控件
這裡的清單控件用來顯示精選衣服基本資訊,使用圖檔加文字的組合方式,我們第一反應想到的是gridview控件,這個想法是對的;可是我們注意到“精選”菜單的整個布局是使用scrollview控件來控制上下一起移動的,如果單純使用gridview控件的話,gridview控件在scrollview中會顯示不正常,是以我們應當自定義girdview讓它不能滑動,并且适配scrollview控件的大小。定義mygridview控件:
在繪制gridview控件大小的時候,通過設定measurespec.at_most參數來指定到想要控件高度,通過onmeasure事件來繪制gridview。在xml布局中引用<com.blog.mogujie.tool.mygridview ../>,布局檔案代碼太長就不貼出來了,待會在後面提供代碼下載下傳連結。
然後再定義該gridview檔案的擴充卡,代碼如下,注意gridview的優化設定:
public class grdoneadapter extends baseadapter{
private context mcontext;
private list<grdoneinfo> mgrdoneinfolist;
public grdoneadapter(context mcontext,list<grdoneinfo> mgrdoneinfolist){
this.mcontext=mcontext;
this.mgrdoneinfolist=mgrdoneinfolist;
return mgrdoneinfolist.size();
public object getitem(int position) {
return position;
public long getitemid(int position) {
public view getview(int position, view convertview, viewgroup parent) {
view view = convertview;
final viewholder holder;
if (convertview == null) {
view = ((activity) mcontext).getlayoutinflater().inflate(
r.layout.item_grd1, parent, false);
holder = new viewholder();
holder.image = (imageview) view.findviewbyid(r.id.grdimage);
holder.brife= (textview) view.findviewbyid(r.id.brife1);
holder.price= (textview) view.findviewbyid(r.id.price);
holder.marks= (textview) view.findviewbyid(r.id.marks);
view.settag(holder);
holder = (viewholder) view.gettag();
holder.image.setimageresource(mgrdoneinfolist.get(position).imagepath);
if(mgrdoneinfolist.get(position).brife.length()>50){
holder.brife.settext(mgrdoneinfolist.get(position).brife.subsequence(0, 30)+"...");
}else{
holder.brife.settext(mgrdoneinfolist.get(position).brife);
holder.price.settext(mgrdoneinfolist.get(position).price);
holder.marks.settext(mgrdoneinfolist.get(position).marksnum);
return view;
static class viewholder{
imageview image;
textview brife;
textview price;
textview marks;
最終效果如下:
資源位址為點選打開連結,今天到這裡。