首頁

HTML2.1表單標(biāo)簽及屬性介紹

seo達(dá)人

如果您想訂閱本博客內(nèi)容,每天自動發(fā)到您的郵箱中, 請點(diǎn)這里

<!DOCTYPE html>

<html>

   <head>

       <meta charset="UTF-8">

       <title>表單標(biāo)簽及屬性介紹</title>

   </head>

   <body>

   <!--form:表單標(biāo)簽,在html頁面創(chuàng)建一個表單(瀏覽器上不顯示),若要提交數(shù)據(jù)到服務(wù)器則負(fù)責(zé)收集數(shù)據(jù)的標(biāo)簽要放到form內(nèi) ;-->

       <!--action:(確定表單提交的路徑);-->

       <!--method提交方式:get(默認(rèn)值)有內(nèi)容 ,post沒有-->

       <form action="#" method="get">


           <!--input:輸入域標(biāo)簽,獲取用戶輸入信息;-->

           <!--type值不同收集方式不同:hidden(隱藏字段,數(shù)據(jù)會發(fā)送到服務(wù)器但瀏覽器不顯示),text(文本框),password(密碼框),radio(單選框),checkbox(復(fù)選框),

               file(文件上傳組件),submit(提交按鈕),button(普通按鈕),reset(重置按鈕);-->

           <!--name:元素名(表單數(shù)據(jù)需提交到服務(wù)器必提供name屬性值),服務(wù)器通過屬性值獲取提交數(shù)據(jù);-->

           <!--readonly:只讀-->

           <!--value:設(shè)置input默認(rèn)值。submit和reset為按鍵上顯示數(shù)據(jù)-->

           <!--size:大小-->

           <!--maxlength:允許輸入的最大長度-->

           隱藏字段:<input type="hidden" name="id" value=""/><br/>

           用戶名:<input type="text" name="username" readonly="readonly" value="zhangsan" size="40px" maxlength="20"/><br/>

           密碼:<input type="password" name="password"/><br/>

           確認(rèn)密碼:<input type="password" name="repassword"/><br/>

           性別:<input type="radio" name="sex" value="man"/>男

           <input type="radio" name="sex" value="woman"/>女<br/>

           愛好:<input type="checkbox" name="hobby" value="釣魚"/>釣魚

           <input type="checkbox" name="hobby" value="打電動"/>打電動

           <!--checked:單選或復(fù)選框默認(rèn)勾選-->

           <input type="checkbox" name="hobby" value="畫畫" checked="checked"/>畫畫<br/>

           頭像:<input type="file" /><br/>

           <!--select:下拉列表標(biāo)簽-->

           籍貫:<select name="province">

               <!--option:子標(biāo)簽,下拉列表中的一個選項(xiàng)-->

               <option>---請選擇---</option>

               <!--value:發(fā)送得服務(wù)器的選項(xiàng)值-->

               <option value="北京">北京</option>

               <option value="上海">上海</option>

               <!--selected:勾選當(dāng)前列表項(xiàng)-->

               <option value="廣州" selected="selected">廣州</option>

           </select><br/>

           自我介紹:

               <!--textarea:文本域-->

               <textarea>


               </textarea><br/>

           提交按鈕:<input type="submit" value="注冊"/><br/>

           普通按鈕:<input type="button" value="普通按鈕"><br/>

           重置按鈕:<input type="reset"/>

       </form>

   </body>

</html>


藍(lán)藍(lán)設(shè)計(jì)www.yvirxh.cn )是一家專注而深入的界面設(shè)計(jì)公司,為期望卓越的國內(nèi)外企業(yè)提供卓越的UI界面設(shè)計(jì)、BS界面設(shè)計(jì) 、 cs界面設(shè)計(jì) 、 ipad界面設(shè)計(jì) 、 包裝設(shè)計(jì) 、 圖標(biāo)定制 、 用戶體驗(yàn) 、交互設(shè)計(jì)、 網(wǎng)站建設(shè) 平面設(shè)計(jì)服務(wù)。


醫(yī)療保健類產(chǎn)品設(shè)計(jì)、界面設(shè)計(jì)及交互設(shè)計(jì)靈感

博博

醫(yī)療保健類產(chǎn)品設(shè)計(jì)、界面設(shè)計(jì)及交互設(shè)計(jì)靈感

UI巴巴 2018-08-03 21:40:30

如果您想訂閱本博客內(nèi)容,每天自動發(fā)到您的郵箱中, 請點(diǎn)這里

今天將從醫(yī)療保健類產(chǎn)品開始延展到互動和交互界面。很多醫(yī)療產(chǎn)品的界面有可能是一個小屏幕,也有可能是非常大的屏幕。

產(chǎn)品

醫(yī)療類的產(chǎn)品我們選擇了一些可穿戴設(shè)備的概念設(shè)計(jì)。

醫(yī)療保健類產(chǎn)品設(shè)計(jì)、界面設(shè)計(jì)及交互設(shè)計(jì)靈感

Kingyo設(shè)計(jì)的Sange手表

醫(yī)療保健類產(chǎn)品設(shè)計(jì)、界面設(shè)計(jì)及交互設(shè)計(jì)靈感

Crux Product Design 和 Chris Pearce 設(shè)計(jì)的

醫(yī)療保健類產(chǎn)品設(shè)計(jì)、界面設(shè)計(jì)及交互設(shè)計(jì)靈感

Amazfit

醫(yī)療保健類產(chǎn)品設(shè)計(jì)、界面設(shè)計(jì)及交互設(shè)計(jì)靈感

Gra?ina Bo?kut?為盲人設(shè)計(jì)的可穿戴配件

交互

我們與不同設(shè)備的交互不斷變化,將語音用戶界面引入醫(yī)療行業(yè)將徹底改變?nèi)藗儗ψo(hù)理的看法。精細(xì)設(shè)計(jì)的語音助理能夠像人一樣,更貼心。

醫(yī)療保健類產(chǎn)品設(shè)計(jì)、界面設(shè)計(jì)及交互設(shè)計(jì)靈感

Michal Sambora設(shè)計(jì)的Alexa助理的界面

醫(yī)療保健類產(chǎn)品設(shè)計(jì)、界面設(shè)計(jì)及交互設(shè)計(jì)靈感

Gleb Kuznetsov?設(shè)計(jì)的ai智能語音助理

醫(yī)療保健類產(chǎn)品設(shè)計(jì)、界面設(shè)計(jì)及交互設(shè)計(jì)靈感

SELECTO設(shè)計(jì)的語音助理

界面

干凈,簡潔,充滿未來感,避免錯誤的發(fā)生。

醫(yī)療保健類產(chǎn)品設(shè)計(jì)、界面設(shè)計(jì)及交互設(shè)計(jì)靈感
醫(yī)療保健類產(chǎn)品設(shè)計(jì)、界面設(shè)計(jì)及交互設(shè)計(jì)靈感
醫(yī)療保健類產(chǎn)品設(shè)計(jì)、界面設(shè)計(jì)及交互設(shè)計(jì)靈感
醫(yī)療保健類產(chǎn)品設(shè)計(jì)、界面設(shè)計(jì)及交互設(shè)計(jì)靈感
醫(yī)療保健類產(chǎn)品設(shè)計(jì)、界面設(shè)計(jì)及交互設(shè)計(jì)靈感
醫(yī)療保健類產(chǎn)品設(shè)計(jì)、界面設(shè)計(jì)及交互設(shè)計(jì)靈感


藍(lán)藍(lán)設(shè)計(jì)www.yvirxh.cn )是一家專注而深入的界面設(shè)計(jì)公司,為期望卓越的國內(nèi)外企業(yè)提供卓越的UI界面設(shè)計(jì)、BS界面設(shè)計(jì) 、 cs界面設(shè)計(jì) 、 ipad界面設(shè)計(jì) 、 包裝設(shè)計(jì) 、 圖標(biāo)定制 、 用戶體驗(yàn) 、交互設(shè)計(jì)、 網(wǎng)站建設(shè) 、平面設(shè)計(jì)服務(wù)

Android MVP極限封裝(一)

seo達(dá)人

如果您想訂閱本博客內(nèi)容,每天自動發(fā)到您的郵箱中, 請點(diǎn)這里

MVP架構(gòu)在Android這一塊已經(jīng)盛行依舊,對于一些學(xué)習(xí)能力比較強(qiáng)的人來說,已經(jīng)能夠運(yùn)用自如甚至改造優(yōu)化了,對于吾等菜鳥,卻是如此的陌生,今日這篇博客,算是小弟在學(xué)習(xí)和應(yīng)用上的一點(diǎn)總結(jié)罷了,如有不足,還請各位大神不吝指教。

MVP架構(gòu)是什么就不多說了,博主主要很大家分享的是,如何設(shè)計(jì)MVP架構(gòu)。

先來分析一下MVP如何使用:M-V-P三層之間,P作為中間層,負(fù)責(zé)M,V之間的數(shù)據(jù)交互的中介,將數(shù)據(jù)從M層獲取,處理之后提交到V層,換句話說,V需要持有P的實(shí)例,P層需要持有V的實(shí)例。原理很簡單,使用泛型對數(shù)據(jù)進(jìn)行封裝處理: 
1.定義一個V層的空接口,主要是方便封裝:

/**
 * V層接口
 */ public interface IView { }
            
  • 1
  • 2
  • 3
  • 4
  • 5

2.定義一個P層的接口:

/**
 * 抽象為接口
 * 
 */ public interface IPresenter<V extends IView> { /**
     * 綁定視圖
     * 
     * @param view
     */ void attachView(V view); /**
     * 解除綁定(每個V記得使用完之后解綁,主要是用于防止內(nèi)存泄漏問題)
     */ void dettachView();

}
            
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

3.封裝P基類:綁定解綁V實(shí)例

/**
 * 抽象類 統(tǒng)一管理View層綁定和解除綁定
 *
 * @param <V>
 */ public class BasePresenter<V extends IView, M extends IModel> implements IPresenter<V> { private WeakReference<V> weakView; protected M model;

    public V getView() { return proxyView;
    } /**
     * 用于檢查View是否為空對象
     *
     * @return */ public boolean isAttachView() { return this.weakView != null && this.weakView.get() != null;
    } @Override public void attachView(V view) { this.weakView = new WeakReference<V>(view);
    } @Override public void dettachView() { if (this.weakView != null) { this.weakView.clear(); this.weakView = null;
        }
    }
}
            
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

4.M層封裝:

/**
 * M層
 */ public interface IModel { } /**
 * 登錄model
 * Created by admin on 2018/2/5.
 */ public interface ILoginModel extends IModel { void login();
} /**
 * 登錄
 * Created by admin on 2018/2/5.
 */ public class LoginModel implements ILoginModel { @Override public void login() { // TODO: 2018/2/5 發(fā)起登錄請求  }
}
            
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

之后,將數(shù)據(jù)提交到activity或者fragment就行了。 
最基本的鋪墊已經(jīng)做好了,接下來就該封裝View了:

/**
 * Created by admin on 2018/2/5.
 */ public abstract class MvpActivity<V extends IView, P extends BasePresenter<V>> extends AppCompatActivity implements IView { private P presenter;

    @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState);
        ...
        presenter=getPresenter();
        presenter.attachView(this);
    } protected P getPresenter() { return presenter;
    } protected void setPresenter(P presenter) { this.presenter = presenter;
    } protected V getView() { return (V) this;
    }
    ...
    @Override protected void onDestroy() {
        presenter.dettachView();
        ... super.onDestroy();
    }
}
            
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

收工,MVP基礎(chǔ)框架搭建完成了。沒錯,就是基礎(chǔ)框架,但是能不能用呢,讓我們拭目以待吧。 
先來寫一個View:

public interface ILoginView extends IView { void onLoginSuccess(); void onFailed();

}
            
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

然后是Presneter:

/**
 * Created by admin on 2018/2/5.
 */ public class LoginPresenter extends BasePresenter<ILogin, LoginModel> { public LoginPresenter() {
        model = new LoginModel();
    }

    public void login(){
        model.login(new LoginCallBack() { @Override public void onSuccess() { if(null!=(ILogin)getView()){
                    weakView.onLoginSuccess();
                }
            } @Override public void onFailure() { if(null!=(ILogin)getView()){
                    weakView.onFailure();
                }
            }
        });
    }

}
            
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

最后來完成Activity的邏輯:

public class LoginActivity extends MvpActivity<ILoginView, LoginPresenter> implements ILoginView { ...
    @Override public LoginPresenter getPresenter() { return new LoginPresenter();
    } public void login(View view) {
        String name = etUserName.getText().toString();
        String pwd = etUserPwd.getText().toString();
        getPresenter().login(name, pwd);
    }

    @Override public void onLoginSuccess() {

    }

    @Override public void onFailed(){

    ...
}


    




    


藍(lán)藍(lán)設(shè)計(jì)www.yvirxh.cn )是一家專注而深入的界面設(shè)計(jì)公司,為期望卓越的國內(nèi)外企業(yè)提供卓越的UI界面設(shè)計(jì)、BS界面設(shè)計(jì) 、 cs界面設(shè)計(jì) 、 ipad界面設(shè)計(jì) 、 包裝設(shè)計(jì) 、 圖標(biāo)定制 、 用戶體驗(yàn) 、交互設(shè)計(jì)、 網(wǎng)站建設(shè) 、平面設(shè)計(jì)服務(wù)。


布局怎么做到不單調(diào)而有層次?來看高手的9個技巧

資深UI設(shè)計(jì)者

如果您想訂閱本博客內(nèi)容,每天自動發(fā)到您的郵箱中, 請點(diǎn)這里

簡單布局怎么做到不單調(diào)而有層次?看看設(shè)計(jì)師 Czékmány Zoltán 的9個技巧。

藍(lán)藍(lán)設(shè)計(jì)www.yvirxh.cn )是一家專注而深入的界面設(shè)計(jì)公司,為期望卓越的國內(nèi)外企業(yè)提供卓越的UI界面設(shè)計(jì)、BS界面設(shè)計(jì) 、 cs界面設(shè)計(jì) 、 ipad界面設(shè)計(jì) 、 包裝設(shè)計(jì) 、 圖標(biāo)定制 、 用戶體驗(yàn) 、交互設(shè)計(jì)、 網(wǎng)站建設(shè) 平面設(shè)計(jì)服務(wù)。

全面易懂!寫給新手的信息架構(gòu)設(shè)計(jì)指南

資深UI設(shè)計(jì)者


如果您想訂閱本博客內(nèi)容,每天自動發(fā)到您的郵箱中, 請點(diǎn)這里

很多產(chǎn)品設(shè)計(jì)師,在畫原型或者設(shè)計(jì) UI 的時候癡迷于工具的使用,拿到需求文檔之后急于動手畫圖,忽略了信息架構(gòu)設(shè)計(jì)對于產(chǎn)品的作用。


信息架構(gòu)作為一個產(chǎn)品的骨架,是產(chǎn)品非常重要的一部分,它決定了一個產(chǎn)品

的布局和未來的發(fā)展方向以及用戶對一個產(chǎn)品的最初印象和整體體驗(yàn)。毫不夸張的說,好的產(chǎn)品信息架構(gòu)是產(chǎn)品成功的一半。

那么到底什么是產(chǎn)品的信息架構(gòu)呢?該如何設(shè)計(jì)產(chǎn)品的信息架構(gòu)?如何評判一個產(chǎn)品信息架構(gòu)的好壞?我們接著往下看:

一、信息架構(gòu)的概念

讓我們來看一個例子:

一個飯店需要有哪些設(shè)施,如果你是飯店的老板如何合理的排布這些設(shè)施,可以讓客戶感覺很舒服的用餐,這個過程就是一個信息架構(gòu)的過程。他可以讓客戶對你的飯店產(chǎn)生好感,從而下次用餐的時候還會想到來你這里吃飯。

在排布飯店設(shè)施的過程中我們要遵循一些規(guī)范,比如用戶的習(xí)慣或者施工規(guī)范等,正是因?yàn)樾枰裱@些規(guī)范,所以我們需要一個信息架構(gòu)來體現(xiàn)這些。

比較官方的信息架構(gòu)解釋是這樣的:信息架構(gòu)設(shè)計(jì)是對信息進(jìn)行結(jié)構(gòu)、組織以及歸類的設(shè)計(jì),好讓使用者與用戶容易使用與理解的一項(xiàng)藝術(shù)與科學(xué)。

簡單來說,信息架構(gòu)設(shè)計(jì)就是讓用戶可以 容易地理解你的產(chǎn)品是怎樣的。讓他們在使用你產(chǎn)品的時候可以更順利更自然。就像一進(jìn)入飯店就會有一種感覺,門口是等餐的地方,進(jìn)去就應(yīng)該吃飯,如果找洗手間一定不會往門口走,而會往深處走。這就是信息架構(gòu)的好處:他讓用戶使用同類產(chǎn)品時更容易上手和理解,讓產(chǎn)品更容易被接受。

二、為何需要信息架構(gòu)設(shè)計(jì)

那對于線上產(chǎn)品來說為什么需要合理的信息架構(gòu)呢?大家來看下邊3組 app 的 tab欄截圖。你能僅僅從 tab欄就看出這款 app 是什么類型的 app,如何使用嗎?

很明顯的,第一個是一款購物類 app,第二個是一款圖片社交類的 app,第三個是微信的 tab,雖然首頁名稱是微信,但是我相信如果把名稱換成「聊天」,你還是能認(rèn)出這是微信的 tab欄。

從底部標(biāo)簽欄就可以大致看出產(chǎn)品是用來干嘛的,這就是信息架構(gòu)的作用。一個合理的信息架構(gòu)可以讓產(chǎn)品非常容易被用戶理解,可以讓用戶第一眼對產(chǎn)品有一個簡單的認(rèn)知,指導(dǎo)自己可以用產(chǎn)品做什么事,指導(dǎo)產(chǎn)品提供什么服務(wù)。

再看一組反例:

這三組 tab欄就讓人很困惑了,看了半天你也許根本不知道這幾款 app 是做什么用的,以及如何使用。如果你讓用戶很困惑,他會分分鐘拋棄你的 app。

所以信息架構(gòu)的核心目標(biāo)是為用戶提供更好的體驗(yàn),獲得更高的留存率。

一款信息架構(gòu)良好的產(chǎn)品必然遵循以下兩個標(biāo)準(zhǔn):

  • 讓用戶打開 app 的第一秒就知道這是一款什么 app,怎么用;
  • 用戶想要使用某一功能時,能夠第一時間找到。

我們通過這兩個標(biāo)準(zhǔn)來印證下上邊3個正面案例的信息架構(gòu):

相信你能很快速的識別出這款軟件的用途和用法,這就給提升留存提供了基礎(chǔ)。

那么如果信息架構(gòu)像架構(gòu)一個飯店一樣簡單,那么信息架構(gòu)為何需要設(shè)計(jì)?

因?yàn)槟愕膶?shí)際產(chǎn)品功能可能有這么多:

畢竟我們不是支付寶,沒辦法把功能像豆腐塊一樣堆疊起來,我們需要一些科學(xué)的設(shè)計(jì)方法。

三、如何設(shè)計(jì)信息架構(gòu)

合理的信息架構(gòu)設(shè)計(jì)需要考慮5個步驟:

下面我來分步講解一下。

1. 了解用戶,場景,習(xí)慣

首先你的產(chǎn)品是給到用戶用,你當(dāng)然要最大限度的了解你的用戶,我們先來看下一個概念:「心智模型」。

心智模型是經(jīng)由經(jīng)驗(yàn)及學(xué)習(xí),腦海中對某些事物發(fā)展的過程,所寫下的劇本。人類在經(jīng)歷或?qū)W習(xí)某些事件之后,會對事物的發(fā)展及變化,歸納出一些結(jié)論,然后像是寫劇本一樣,把這些經(jīng)驗(yàn)濃縮成一本一本的劇本,等到重復(fù)或類似的事情再度發(fā)生,我們便不自覺的應(yīng)用這些先前寫好的劇本,來預(yù)測事物的發(fā)展變化。心智模型是你對事物運(yùn)行發(fā)展的預(yù)測。再說得清楚一點(diǎn),你「希望」事物將如何發(fā)展,并不是心智模型,但你「認(rèn)為」事物將如何發(fā)展,就是你的心智模型了。

假設(shè)你從沒見過 iPad,而我剛遞給你一臺并告訴你可以用它來看書。在你打開 iPad 使用它之前,你頭腦里會有一個在 iPad 上如何閱讀的模型。你會假想書在 iPad 屏幕上是怎樣的,你可以做什么事情,比如翻頁或使用書簽,以及這些事情的大致做法。即使你以前從沒有使用過 iPad,你也有一個用 iPad 看書的「心智模型」。你頭腦里的心智模型的樣式和運(yùn)作方式取決于很多因素。

用戶往往帶著以往使用 APP 的一些習(xí)慣來使用產(chǎn)品;線下做同一件事的習(xí)慣、生活習(xí)慣、心智模型等。要考慮哪些是可以創(chuàng)新的,哪些是用戶習(xí)慣,要在不妨礙用戶習(xí)慣的情況下作出更能讓用戶接受的創(chuàng)新。

你要考慮清楚4個問題:

用戶通常用你的產(chǎn)品做什么?

用戶用你的產(chǎn)品來做什么?用來看新聞還是用來聊天?一定要考慮清楚用戶的核心流程。從核心流程中提取信息架構(gòu)的基礎(chǔ)形式。

用戶用這類產(chǎn)品最關(guān)心什么?

用新聞app 時咨詢的真實(shí)性實(shí)效性,購物類app 精準(zhǔn)搜索和售后功能,就是你的用戶關(guān)注點(diǎn)在哪里,這是一個很好的突破口。

用戶有哪些思維定式?

和用戶年齡身份相關(guān)的屬性,產(chǎn)品體驗(yàn)符合相應(yīng)用戶的思維模式,心智模型,用戶就會比較容易接受。

用戶用什么類似的產(chǎn)品?

類似的產(chǎn)品也會帶來一些用戶習(xí)慣,迎合這些習(xí)慣也會讓用戶快速上手接受產(chǎn)品。

了解了你的用戶場景和使用習(xí)慣之后你會知道如何做出符合用戶心智的,容易被接受的產(chǎn)品,你不需要擔(dān)心做的產(chǎn)品沒有差異性或者沒有競爭力,我們可以在核心流程之外做出創(chuàng)新點(diǎn),讓用戶覺得你的產(chǎn)品又好用又有些不一樣。

2. 了解業(yè)務(wù)

這里的業(yè)務(wù)包括與產(chǎn)品接觸的內(nèi)部及外部的人提出的需求,比如公司的運(yùn)營,市場,銷售,BD,公司的外部合作伙伴等。

這些人的需求我們也要收集,比如運(yùn)營人員想更方便的管理注冊用戶,銷售想更多的添加廣告位,市場推廣人員要求能統(tǒng)計(jì)不同渠道帶來產(chǎn)品的下載量,注冊數(shù),活躍數(shù),合作伙伴需要進(jìn)行賬號,內(nèi)容互通等,總之只要與業(yè)務(wù)有關(guān)的人的意見,盡可能的在產(chǎn)品設(shè)計(jì)前多收集,即使做不了,也告訴他們原因,要不然產(chǎn)品上線后就等著被他們吐槽吧。

3. 調(diào)研競品的信息架構(gòu)

在做一款 app 時,我們面臨了和無數(shù)競品爭搶用戶的局面,這時候分析競品就非常必要,我們需要在知己知彼的前提下,做好核心流程功能,再思考如何在差異功能上做好突破。

首先我們需要把競品功能梳理成思維導(dǎo)圖:

其實(shí)思維導(dǎo)圖就是信息架構(gòu)比較基礎(chǔ)的形式了,但是光有思維導(dǎo)圖沒用,我們需要對思維導(dǎo)圖進(jìn)行分析。

我以前做過的一款人脈 app 為例,當(dāng)初對比了領(lǐng)英、赤兔和脈脈,分析了這4款 app 的思維導(dǎo)圖后得出的共性和差異點(diǎn):

共性就是要符合用戶使用習(xí)慣的地方,如果你調(diào)研的3-5個產(chǎn)品都這么做了,很可能這里是產(chǎn)生用戶習(xí)慣的地方,是我們需要去遵循的,這是獲得用戶好感度的基礎(chǔ)。

分析產(chǎn)品時你一定也會得出一些產(chǎn)品差異的地方,而這些差異就是你的產(chǎn)品競爭點(diǎn),也是別人用你的 app 不用其他 app 的理由。比如人脈軟件都會有社交相關(guān)的功能,但是脈脈會比較注重職場招聘、直播等互聯(lián)網(wǎng)職場人比較關(guān)心的點(diǎn),這樣對應(yīng)的用戶群體就比較會吃你這套,會提升用戶的粘性。

相信你在梳理了競品的信息架構(gòu),總結(jié)了共性和差異點(diǎn)之后對產(chǎn)品的信息架構(gòu)已經(jīng)有一個比較清晰的認(rèn)知了,在做自己產(chǎn)品信息架構(gòu)的時候也會更胸有成竹。但是最后還有一件事我們可以做,就是對我們的要做的產(chǎn)品功能做卡片分類。

4. 卡片分類

卡片分類法是我們工作中常用到的一種方法,它可以在用戶側(cè)再一次印證和檢測我們的產(chǎn)品信息架構(gòu)。

卡片分類法就是讓用戶對功能卡片進(jìn)行分類,組織,并給相關(guān)功能的集合重新定義名稱的一種自下而上的整理方法。

說直白點(diǎn)就是準(zhǔn)備一堆卡片,在這些卡片上寫上你所需要包含的功能名稱,然后給到用戶側(cè),讓用戶進(jìn)行分類,讓用戶進(jìn)行組織,來了解用戶到底覺得這些功能應(yīng)該怎么合并怎么歸類的一種方法。它可以幫助你站在用戶角度去了解用戶是怎么認(rèn)定這些功能的,也可以在卡片分類法的過程中更加了解用戶是怎么想的。

卡片分類法大概的步驟和注意點(diǎn)是這樣的:

卡片分類法最終會產(chǎn)出這樣的一個樹形圖:

5. 產(chǎn)出信息架構(gòu)

其實(shí)到這一步信息架構(gòu)大概的雛形已經(jīng)有了,你可以用 axure 或者類似 mindnode 的軟件把信息架構(gòu)梳理出來。

接下來你要對信息架構(gòu)進(jìn)行重要性分級,這樣在產(chǎn)品開發(fā)的前期可以幫助梳理產(chǎn)品研發(fā)的優(yōu)先級,集中精力解決用戶的最大痛點(diǎn)。在產(chǎn)出頁面時也可以更好的把控頁面元素的大小層級,位置關(guān)系等。

最后你需要注意層和度的平衡:層一般不超過5層,超過操作困難。度過多會讓用戶認(rèn)知成本增加,容易找不到想找的內(nèi)容。這里的度指的是同一頁面展示的信息量。


藍(lán)藍(lán)設(shè)計(jì)www.yvirxh.cn )是一家專注而深入的界面設(shè)計(jì)公司,為期望卓越的國內(nèi)外企業(yè)提供卓越的UI界面設(shè)計(jì)、BS界面設(shè)計(jì) 、 cs界面設(shè)計(jì) 、 ipad界面設(shè)計(jì) 、 包裝設(shè)計(jì) 、 圖標(biāo)定制 、 用戶體驗(yàn) 、交互設(shè)計(jì)、 網(wǎng)站建設(shè) 、平面設(shè)計(jì)服務(wù)


Retrofit源碼分析

seo達(dá)人

如果您想訂閱本博客內(nèi)容,每天自動發(fā)到您的郵箱中, 請點(diǎn)這里

1、簡介

retrofit是一個封裝okhttp請求的網(wǎng)絡(luò)請求庫,可以通過Rxjava適配返回信息。

2、原理分析

我們通過Retrofit.Builder建造者模式創(chuàng)建一個Retrofit實(shí)例對象

public static final class Builder {
    /**
      *Android線程切換的類 
      */
    private final Platform platform;
    private @Nullable okhttp3.Call.Factory callFactory;
    private HttpUrl baseUrl;
    private final List<Converter.Factory> converterFactories = new ArrayList<>();
    private final List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>();
    private @Nullable Executor callbackExecutor;
    private boolean validateEagerly;

    Builder(Platform platform) {
      this.platform = platform;
    }

    public Builder() {
      this(Platform.get());
    }

    Builder(Retrofit retrofit) {
      platform = Platform.get();
      callFactory = retrofit.callFactory;
      baseUrl = retrofit.baseUrl;

      converterFactories.addAll(retrofit.converterFactories);
      // Remove the default BuiltInConverters instance added by build().
      converterFactories.remove(0);

      callAdapterFactories.addAll(retrofit.callAdapterFactories);
      // Remove the default, platform-aware call adapter added by build().
      callAdapterFactories.remove(callAdapterFactories.size() - 1);

      callbackExecutor = retrofit.callbackExecutor;
      validateEagerly = retrofit.validateEagerly;
    }

    public Builder client(OkHttpClient client) {
      return callFactory(checkNotNull(client, "client == null"));
    }

    public Builder callFactory(okhttp3.Call.Factory factory) {
      this.callFactory = checkNotNull(factory, "factory == null");
      return this;
    }

    public Builder baseUrl(String baseUrl) {
      checkNotNull(baseUrl, "baseUrl == null");
      HttpUrl httpUrl = HttpUrl.parse(baseUrl);
      if (httpUrl == null) {
        throw new IllegalArgumentException("Illegal URL: " + baseUrl);
      }
      return baseUrl(httpUrl);
    }

    public Builder baseUrl(HttpUrl baseUrl) {
      checkNotNull(baseUrl, "baseUrl == null");
      List<String> pathSegments = baseUrl.pathSegments();
      if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
        throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
      }
      this.baseUrl = baseUrl;
      return this;
    }

    public Builder addConverterFactory(Converter.Factory factory) {
      converterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }

    public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
      callAdapterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }

    public Builder callbackExecutor(Executor executor) {
      this.callbackExecutor = checkNotNull(executor, "executor == null");
      return this;
    }

    public List<CallAdapter.Factory> callAdapterFactories() {
      return this.callAdapterFactories;
    }

    public List<Converter.Factory> converterFactories() {
      return this.converterFactories;
    }

    public Builder validateEagerly(boolean validateEagerly) {
      this.validateEagerly = validateEagerly;
      return this;
    }

    public Retrofit build() {
      if (baseUrl == null) {
        throw new IllegalStateException("Base URL required.");
      }

      okhttp3.Call.Factory callFactory = this.callFactory;
      if (callFactory == null) {
        callFactory = new OkHttpClient();
      }

      Executor callbackExecutor = this.callbackExecutor;
      if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
      }

      // Make a defensive copy of the adapters and add the default Call adapter.
      List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
      callAdapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

      // Make a defensive copy of the converters.
      List<Converter.Factory> converterFactories =
          new ArrayList<>(1 + this.converterFactories.size());

      // Add the built-in converter factory first. This prevents overriding its behavior but also
      // ensures correct behavior when using converters that consume all types.
      converterFactories.add(new BuiltInConverters());
      converterFactories.addAll(this.converterFactories);

      return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
          unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
    }
 } 
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129

通過Retrofit.Builder中build方法創(chuàng)建一個Retrofit實(shí)例對象,在創(chuàng)建Retrofit時會判斷用戶創(chuàng)建OkhttpClient對象,沒有創(chuàng)建Retrofit會創(chuàng)建一個默認(rèn)okhttpClient對象,然后設(shè)置Platform中的主線程線程池,設(shè)置線程池處理器交給主線程Looper對象。然后創(chuàng)建一個Retrofit對象。我們通過Retrofit.create創(chuàng)建一個接口代理類

 public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();

          @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.adapt(okHttpCall);
          }
        });
  } 
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

在調(diào)用Creater方法時,通過代理類創(chuàng)建Service實(shí)例對象,當(dāng)我們通過接口實(shí)例對象調(diào)用方法時,通過invoke方法時,通過Method創(chuàng)建一個ServiceMethod對象,然后把ServiceMethod存儲起來

 public ServiceMethod build() {
          callAdapter = createCallAdapter();
          responseType = callAdapter.responseType();
          if (responseType == Response.class || responseType == okhttp3.Response.class) {
            throw methodError("'"
                + Utils.getRawType(responseType).getName()
                + "' is not a valid response body type. Did you mean ResponseBody?");
          }
          responseConverter = createResponseConverter();

          for (Annotation annotation : methodAnnotations) {
            parseMethodAnnotation(annotation);
          }

          if (httpMethod == null) {
            throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
          }

          if (!hasBody) {
            if (isMultipart) {
              throw methodError(
                  "Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
            }
            if (isFormEncoded) {
              throw methodError("FormUrlEncoded can only be specified on HTTP methods with "
                  + "request body (e.g., @POST).");
            }
          }

          int parameterCount = parameterAnnotationsArray.length;
          parameterHandlers = new ParameterHandler<?>[parameterCount];
          for (int p = 0; p < parameterCount; p++) {
            Type parameterType = parameterTypes[p];
            if (Utils.hasUnresolvableType(parameterType)) {
              throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
                  parameterType);
            }

            Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
            if (parameterAnnotations == null) {
              throw parameterError(p, "No Retrofit annotation found.");
            }

            parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
          }

          if (relativeUrl == null && !gotUrl) {
            throw methodError("Missing either @%s URL or @Url parameter.", httpMethod);
          }
          if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
            throw methodError("Non-body HTTP method cannot contain @Body.");
          }
          if (isFormEncoded && !gotField) {
            throw methodError("Form-encoded method must contain at least one @Field.");
          }
          if (isMultipart && !gotPart) {
            throw methodError("Multipart method must contain at least one @Part.");
          }

          return new ServiceMethod<>(this);
        }

    private CallAdapter<T, R> createCallAdapter() {
            /**
             *獲取方法返回值類型
             */
          Type returnType = method.getGenericReturnType();
          if (Utils.hasUnresolvableType(returnType)) {
            throw methodError(
                "Method return type must not include a type variable or wildcard: %s", returnType);
          }
          if (returnType == void.class) {
            throw methodError("Service methods cannot return void.");
          }
          //獲取注解信息
          Annotation[] annotations = method.getAnnotations();
          try {
            //noinspection unchecked
            return (CallAdapter<T, R>) retrofit.callAdapter(returnType, annotations);
          } catch (RuntimeException e) { // Wide exception range because factories are user code.
            throw methodError(e, "Unable to create call adapter for %s", returnType);
          }
        } 
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85

在創(chuàng)建ServiceMethod時,獲取我們okhttp請求是否有返回值,沒有返回值拋出異常,然后獲取注解信息,然后獲取retrofit中CallAdapter.Factory,然后調(diào)用get方法,我們在通過rxjavaFactoryAdapter.create創(chuàng)建的就是實(shí)現(xiàn)CallAdapter.Factory對象,然后調(diào)用CallAdapter.Factory中respenseType方法,然后通過我們傳遞converter對數(shù)據(jù)進(jìn)行序列化,可以通過gson和fastjson進(jìn)行實(shí)例化對象,然后通過parseMethodAnnomation解析請求類型

 private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
          if (this.httpMethod != null) {
            throw methodError("Only one HTTP method is allowed. Found: %s and %s.",
                this.httpMethod, httpMethod);
          }
          this.httpMethod = httpMethod;
          this.hasBody = hasBody;

          if (value.isEmpty()) {
            return;
          }

          // Get the relative URL path and existing query string, if present.
          int question = value.indexOf('?');
          if (question != -1 && question < value.length() - 1) {
            // Ensure the query string does not have any named parameters.
            String queryParams = value.substring(question + 1);
            Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams);
            if (queryParamMatcher.find()) {
              throw methodError("URL query string \"%s\" must not have replace block. "
                  + "For dynamic query parameters use @Query.", queryParams);
            }
          }

          this.relativeUrl = value;
          this.relativeUrlParamNames = parsePathParameters(value);
        } 
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

通過注解類型獲取到請求類型時,通過調(diào)用相關(guān)方法解析獲取到請求url,然后通過注解獲取方法中是否有注解字段,有注解信息存儲到Set集合中。然后創(chuàng)建一個OkhttpCall對象,通過調(diào)用serviceMethod.adapt方法做網(wǎng)絡(luò)請求,serviceMethod.adapt調(diào)用是callAdapter中的adapt方法,如果用戶沒有設(shè)置callAdapter模式使用的是ExecutorCallAdapterFactory中的adapt方法

 public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
            if (getRawType(returnType) != Call.class) {
                return null;
            } else {
                final Type responseType = Utils.getCallResponseType(returnType);
                return new CallAdapter<Object, Call<?>>() {
                    public Type responseType() {
                        return responseType;
                    }

                    public Call<Object> adapt(Call<Object> call) {
                        return new ExecutorCallAdapterFactory.ExecutorCallbackCall(ExecutorCallAdapterFactory.this.callbackExecutor, call);
                    }
                };
            }
        } 
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

在ExectorCallAdapterFactory中調(diào)用組裝的Call方法中enqueue方法調(diào)用異步網(wǎng)絡(luò)請求,成功后通過Platform中MainThreadExecutor切換到主線程。在調(diào)用callback中的enqueue,onResponse和onFairlure方法時實(shí)際是調(diào)用到OkhttpCall方法的onResponse方法,在OkHttpCall.enqueue中重新組建OkHttp.Call url和參數(shù)信息,然后封裝請求,請求成功后通過parseResponse解析返回信息狀態(tài),然后把返回信息狀態(tài)成ResponseBody對象,調(diào)用ServiceMethod.toResponse解析,在toResponse中實(shí)際是我們設(shè)置ConverterFactory對象解析數(shù)據(jù),完成后調(diào)用callBack中onSuccess方法。

 @Override public void enqueue(final Callback<T> callback) {
        checkNotNull(callback, "callback == null");

        okhttp3.Call call;
        Throwable failure;

        synchronized (this) {
          if (executed) throw new IllegalStateException("Already executed.");
          executed = true;

          call = rawCall;
          failure = creationFailure;
          if (call == null && failure == null) {
            try {
              call = rawCall = createRawCall();
            } catch (Throwable t) {
              throwIfFatal(t);
              failure = creationFailure = t;
            }
          }
        }

        if (failure != null) {
          callback.onFailure(this, failure);
          return;
        }

        if (canceled) {
          call.cancel();
        }

        call.enqueue(new okhttp3.Callback() {
          @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
            Response<T> response;
            try {
              response = parseResponse(rawResponse);
            } catch (Throwable e) {
              callFailure(e);
              return;
            }

            try {
              callback.onResponse(OkHttpCall.this, response);
            } catch (Throwable t) {
              t.printStackTrace();
            }
          }

          @Override public void onFailure(okhttp3.Call call, IOException e) {
            callFailure(e);
          }

          private void callFailure(Throwable e) {
            try {
              callback.onFailure(OkHttpCall.this, e);
            } catch (Throwable t) {
              t.printStackTrace();
            }
          }
        });
      }
藍(lán)藍(lán)設(shè)計(jì)www.yvirxh.cn )是一家專注而深入的界面設(shè)計(jì)公司,為期望卓越的國內(nèi)外企業(yè)提供卓越的UI界面設(shè)計(jì)、BS界面設(shè)計(jì) 、 cs界面設(shè)計(jì) 、 ipad界面設(shè)計(jì) 、 包裝設(shè)計(jì) 、 圖標(biāo)定制 、 用戶體驗(yàn) 、交互設(shè)計(jì)、 網(wǎng)站建設(shè) 、平面設(shè)計(jì)服務(wù)

如何用最簡單的點(diǎn)線面,解決沒靈感?

ui設(shè)計(jì)分享達(dá)人

點(diǎn)、線、面和構(gòu)成手法,就像是大廈的基石一樣,看起來毫不起眼,但力量卻無比強(qiáng)大。

2017【百度Doodle 設(shè)計(jì)盤點(diǎn)】中秋節(jié)

ui設(shè)計(jì)分享達(dá)人

是百度專門為重大節(jié)日或紀(jì)念日設(shè)計(jì)制作的動態(tài)百度Logo

我們特此盤點(diǎn)了2017年的百度Doodle設(shè)計(jì)

日歷

鏈接

個人資料

存檔