所謂知之者不如好之者,好之者不如樂(lè)之者。要想持之以恒,最佳的狀態(tài)便是樂(lè)在其中。本文圖文并茂,帶你從Android Fragment最佳實(shí)踐的“全世界”路過(guò)。
先貼上效果圖:
這是一個(gè)關(guān)于水果的項(xiàng)目(麻雀雖小,五臟俱全嘛(;???)=3????):
整個(gè)界面分為兩個(gè)部分,左半部分是一個(gè)列表,用于顯示水果的名稱。
右半部分則用來(lái)顯示水果的名稱及簡(jiǎn)介。
既然是面向?qū)ο缶幊蹋?br>
我們首先就需要建立一個(gè)水果的實(shí)體類Fruit來(lái)模擬這些水果。
不過(guò),由于我們需要對(duì)這些水果進(jìn)行管理,所以這里我們考慮建立一個(gè)名為AllFruit的外部類來(lái)對(duì)這些水果進(jìn)行管理。
當(dāng)然,并不是說(shuō)非得建立這種內(nèi)外部類的關(guān)系,只是這樣的設(shè)計(jì)更加自然,符合邏輯。
代碼如下:
public class AllFruit {
//定義內(nèi)部類Fruit
public static class Fruit{//Fruit類開(kāi)始
//Fruit類的成員變量
public Integer id;
public String name;
public String description;
//Fruit類的構(gòu)造器
public Fruit(Integer id, String name, String description){
this.id = id;
this.name = name;
this.description = description;
}
//toString方法可以理解為水果對(duì)自己的介紹。這里我們重寫之后,
//當(dāng)直接輸出水果對(duì)象時(shí)會(huì)調(diào)用此方法,此處輸出水果的name屬性
@Override
public String toString() {
return name;
}
}//Fruit類結(jié)束
//用list集合來(lái)記錄所有的水果對(duì)象
public static List<Fruit> FRUIT_LIST_ITEMS = new ArrayList<>();
//使用map集合來(lái)記錄所有的水果對(duì)象
public static Map<Integer,Fruit> FRUIT_MAP_ITEMS = new HashMap<>();
static {
//通過(guò)靜態(tài)初始化塊來(lái)事先定義List和Map集合中的內(nèi)容
//添加Fruit對(duì)象的方法是:用封裝好的addItem()方法來(lái)添加:
addItem(new Fruit(1,"Apple","蘋果" +
"是薔薇科蘋果亞科蘋果屬植物,其樹(shù)為落葉喬木。" +
"蘋果的果實(shí)富含礦物質(zhì)和維生素,是人們最常食用的水果之一。"));
addItem(new Fruit(2,"Pear","梨是一種水果的名稱,薔薇科梨屬植物," +
"多年生落葉喬木果樹(shù),葉子卵形,花多白色," +
"一般梨的顏色為外皮呈現(xiàn)出金黃色或暖黃色," +
"里面果肉則為通亮白色,鮮嫩多汁,口味甘甜," +
"核味微酸,是很好的水果。很多分布在華北、東北、西北及長(zhǎng)江流域各省。"));
addItem(new Fruit(3,"Banana","香蕉,又稱甘蕉、芎蕉、芽蕉,弓蕉," +
"為芭蕉科芭蕉屬小果野蕉的人工栽培雜交種,為多年生草本植物。" +
"果實(shí)長(zhǎng)有棱;果皮黃色,果肉白色,味道香甜。" +
"主要生長(zhǎng)在熱帶、亞熱帶地區(qū)。原產(chǎn)于亞洲東南部熱帶、亞熱帶地區(qū)。"));
addItem(new Fruit(4,"grape","葡萄為葡萄科葡萄屬木質(zhì)藤本植物,小枝圓柱形,有縱棱紋," +
"無(wú)毛或被稀疏柔毛,葉卵圓形,圓錐花序密集或疏散,基部分枝發(fā)達(dá)," +
"果實(shí)球形或橢圓形,花期4-5月,果期8-9月。"+
"葡萄是世界最古老的果樹(shù)樹(shù)種之一,葡萄的植物化石發(fā)現(xiàn)于第三紀(jì)地層中," +
"說(shuō)明當(dāng)時(shí)已遍布于歐、亞及格陵蘭。[1] 葡萄原產(chǎn)亞洲西部,世界各地均有栽培,[2] " +
"世界各地的葡萄約95%集中分布在北半球。"));
}
//封裝在集合中添加水果對(duì)象的方法
private static void addItem(Fruit fruit){
FRUIT_LIST_ITEMS.add(fruit);
FRUIT_MAP_ITEMS.put(fruit.id,fruit);
}
}
用圖示來(lái)說(shuō)明的話,其邏輯是醬嬸兒的:
一目了然,外部類AllFruit管理著內(nèi)部類Fruit。
- AllFruit將Fruit類的信息以兩種方式保存:List和Map;
- 如果別人來(lái)找AllFruit要Fruit的信息,則AllFruit就會(huì)根據(jù)具體情況來(lái)選擇不同的方式以交付信息
注:關(guān)于靜態(tài)初始化塊的說(shuō)明:
- Java 中可以通過(guò)初始化塊進(jìn)行數(shù)據(jù)賦值;
- 靜態(tài)初始化塊只在類(本例中是AllFruit類)加載時(shí)執(zhí)行,且只會(huì)執(zhí)行一次,同時(shí)靜態(tài)初始化塊只能給靜態(tài)變量賦值,不能初始化普通的成員變量。
第二步:
既然已經(jīng)定義好了實(shí)體類,那我們就要開(kāi)始考慮項(xiàng)目的兩個(gè)部分了。
這里我們先考慮右半部分的實(shí)現(xiàn):
這部分實(shí)際是一個(gè)包含兩個(gè)文本框的Fragment:
先貼出其布局文件:
<?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">
<!--定義一個(gè)TextView來(lái)顯示水果名稱-->
<TextView
android:id="@+id/fruit_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="40sp"
android:layout_marginBottom="5dp"
android:padding="20dp"
android:textColor="@color/colorAccent"/>
<!--再定義一個(gè)TextView來(lái)顯示水果的簡(jiǎn)介-->
<TextView
android:id="@+id/fruit_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp"
android:padding="30dp"/>
就定義了兩個(gè)文本框而已。十分簡(jiǎn)單,不再贅述。
重點(diǎn)是我們要怎樣將布局文件引入Fragment呢?
Android已經(jīng)為我們做好了安排:
public class FruitTitleAndDescFragment extends android.app.Fragment {
public static final String ITEM_ID = "item_id";
//在全局變量中保存 這個(gè)Fragment將要顯示的Fruit對(duì)象
AllFruit.Fruit fruit;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments().containsKey(ITEM_ID)){
fruit = AllFruit.FRUIT_MAP_ITEMS
.get(getArguments().getInt(ITEM_ID));
}
}
//重寫onCreateView()方法,此方法返回的view將作為Fragment顯示的組件
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View parentView = inflater.inflate(
R.layout.fruit_name_and_desc,
container,
false);
//fruit為之前定義的全局變量
if (fruit!= null){
//讓id為fruit_name的文本框顯示水果的name
//注意setText()方法前面有幾個(gè)括號(hào)
((TextView)parentView
.findViewById(R.id.fruit_name))
.setText(fruit.name);
//讓id為fruit_desc的文本框顯示水果的desc(描述信息)
((TextView)parentView
.findViewById(R.id.fruit_desc))
.setText(fruit.description);
}
return parentView;
}
}
這里我們定義了一個(gè)FruitTitleAndDescFragment類,并讓其繼承自Fragment類。
并且重寫了onCreate()方法和onCreateView()方法。
其中,onCreateView()方法就是用來(lái)將Fragment的實(shí)現(xiàn)類與其布局進(jìn)行綁定的。
具體方法為:
- 調(diào)用onCreateView方法傳入的inflater對(duì)象的inflate方法,
傳入三個(gè)參數(shù),其中后兩個(gè)一般都是container和false,而本例子中的R.layout.fruit_name_and_desc正是我們想要加載的布局。這樣我們就得到了根布局,這里我們將其命名為parentView。 - 再通過(guò)parentView的findViewById()方法就可以得到各個(gè) 子view
- 然后我們讓得到的兩個(gè)文本框分別顯示Fruit對(duì)象的name和description屬性
- 從這里我們也可以看出來(lái) onCreateView()方法和單純的setContentView()方法的區(qū)別:
調(diào)用后者只是為了加載布局,而調(diào)用前者則是要兼顧加載布局和對(duì)布局中組件的行為進(jìn)行設(shè)置。比如此處我們便設(shè)置了文本框顯示的內(nèi)容。
有一定英語(yǔ)基礎(chǔ)的童鞋應(yīng)該很好理解:所謂onCreateView所表達(dá)的,正是view被Create之后,該做什么! - 最后,別忘了要將parentView返回。
而onCreate()方法則是在創(chuàng)建Fragment對(duì)象時(shí)被調(diào)用。
因?yàn)榕c后面的邏輯聯(lián)系較緊密,因此其中的代碼我們稍后再作討論。
第三步:
現(xiàn)在我們?cè)賮?lái)考慮左半部分的實(shí)現(xiàn)。
左半部分是一個(gè)列表??赡苡型牡谝环磻?yīng)就是用ListView來(lái)實(shí)現(xiàn)。但其實(shí)Android中已經(jīng)為我們準(zhǔn)備好了一個(gè)ListFragment類。讓我們的活動(dòng)繼承自這個(gè)類,就能輕松實(shí)現(xiàn)列表的形式:
//直接繼承自ListFragment
public class FruitListFragment extends ListFragment{
//接口的實(shí)例
private Callback myCallback;
//在FruitListFragment中定義一個(gè)接口
public interface Callback{
void onItemClicked (Integer id);
}//接口結(jié)束
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//為該ListFragment設(shè)置Adapter
setListAdapter(new ArrayAdapter<AllFruit.Fruit>(
getActivity(),
android.R.layout.simple_list_item_activated_1,
android.R.id.text1,
AllFruit.FRUIT_LIST_ITEMS));
}//onCreate()方法結(jié)束
// 當(dāng)Fragment被添加、顯示到Activity中時(shí),回調(diào)此方法
@Override
public void onAttach(Context context) {
super.onAttach(context);
// 如果Activity沒(méi)有實(shí)現(xiàn)Callback接口,則拋出異常
if (!(context instanceof Callback)){
throw new IllegalStateException(
"FruitListFragment 所在的Activity必須實(shí)現(xiàn)Callback接口"
);
}
//把該Activity當(dāng)做Callback對(duì)象
myCallback = (Callback) context;
}//onAttach()結(jié)束
// 當(dāng)該Fragment從它所屬的Activity中被刪除時(shí)回調(diào)此方法
@Override
public void onDetach() {
super.onDetach();
// 給myCallback賦空值
myCallback = null;
}//onDetach()結(jié)束
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
// 觸發(fā)myCallback的onItemclicked()方法
myCallback.onItemClicked(AllFruit.FRUIT_LIST_ITEMS.get(position).id);
}//onListItemClick()結(jié)束
}
這里我們定義了一個(gè)FruitListFragment類。
并重寫了四個(gè)方法。
下面一一講解:
- onCreate()方法很好理解:
- 其規(guī)定了本ListFragment被創(chuàng)建時(shí)應(yīng)該執(zhí)行那些操作。
- 這里我們只做了一件事: 就是調(diào)用setListAdapter()方法,為要顯示的列表指定了Adapter,以綁定數(shù)據(jù)源。
- 為setListAdapter()方法傳入的是ArrayAdapter的實(shí)例:
可以看到,ArrayAdapter共有5個(gè)重載的構(gòu)造器,我們選用的正是第五個(gè)。
- 第一個(gè)參數(shù)是context。這個(gè)是自然;
第二個(gè)參數(shù)是resource,其實(shí)也就是列表中每一項(xiàng)的Layout;
第三個(gè)參數(shù)是textViewResourceId,指的是列表中每一項(xiàng)所包含的TextView的樣子(我們的水果名正是用TextView顯示出來(lái)的)。
第二個(gè)和第三個(gè)參數(shù),在本例中都是引用的Android內(nèi)置的主題。 - 最后一個(gè)參數(shù)最重要,因?yàn)槠涮峁┝藬?shù)據(jù)的來(lái)源。這里我們指定為AllFruit.FRUIT_LIST_ITEMS。也就是先前在AllFruit中定義的list集合。
- onAttach()方法在Fragment被添加、顯示到Activity中時(shí)回調(diào)。
- “Attach”表示附著。正體現(xiàn)了Fragment與Activity的聯(lián)系。
- 可以看到,回調(diào)時(shí)傳入了一個(gè)context對(duì)象,由于Activity是Context的子類,所以可以此處傳入的context對(duì)象所指代的,正是該Fragment所依附的Activity。
- 并且這里我們將這個(gè)傳入的活動(dòng)賦給了一個(gè)接口對(duì)象,關(guān)于這樣做的目的,以及在FruitListFragment內(nèi)部定義一個(gè)名為Callback接口的意義,稍后再做闡釋?,F(xiàn)在先記住,myCallback是一個(gè)全局變量,也就是說(shuō)它的作用域?yàn)檎麄€(gè)類。
- onDetach()方法與onAttach()方法正相反
- 所以,正因?yàn)樵趏nAttach()方法中將當(dāng)前與Fragment產(chǎn)生聯(lián)系的Activity賦給了myCallback。在onDetach()方法中,當(dāng)Fragment與Activity不再發(fā)生聯(lián)系時(shí),就為myCallback賦空值以示關(guān)系的脫離。
- onListItemClick()方法顧名思義,應(yīng)當(dāng)在FruitListFragment中的list的子項(xiàng)被點(diǎn)擊時(shí)被回調(diào)。
...
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
// 觸發(fā)myCallback的onItemclicked()方法
myCallback.onItemClicked(
AllFruit.FRUIT_LIST_ITEMS.get(position).id);
}//onListItemClick()結(jié)束
可以看到,我們?cè)谄浞椒w中調(diào)用了 Callback 接口中定義的方法:
onItemClicked(Integer id)
旨在設(shè)置當(dāng)list的子項(xiàng)被點(diǎn)擊時(shí)應(yīng)該執(zhí)行的邏輯。
那么問(wèn)題就來(lái)了。
既然onItemClicked(Integer id)方法被定義在接口中,那么它自然是一個(gè)抽象方法。
那么這個(gè)抽象方法應(yīng)該由誰(shuí)來(lái)實(shí)現(xiàn)呢?當(dāng)然是由實(shí)現(xiàn)了接口的那個(gè)類來(lái)實(shí)現(xiàn)了。
那么又由誰(shuí)來(lái)實(shí)現(xiàn)該接口呢?
答案是:MainActivity
(跑得太遠(yuǎn),都快忘了還有MainActivity存在了)
那么這就關(guān)系到MainActivity存在的初衷了。
我們需要MainActivity來(lái)做什么呢?
先看看其布局文件:
<?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:divider="?dividerHorizontal"
android:orientation="horizontal">
<fragment
android:id="@+id/fruit_list" android:name="com.example.feverdg.fragmenttest.FruitListFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<FrameLayout
android:id="@+id/fruit_desc_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3" />
</LinearLayout>
可以看到,這其中包括我們事先定義好的FruitListFragment片段。用于顯示列表項(xiàng)。
而下面這個(gè)卻不是fragment標(biāo)簽,而是一個(gè)FrameLayout的標(biāo)簽
等等,那我們之前
public class FruitTitleAndDescFragment extends android.app.Fragment { ... }
定義FruitTitleAndDescFragment片段定義了半天是為了什么?
當(dāng)然是為了顯示了??墒菫槭裁床挥胒ragment標(biāo)簽來(lái)引用呢?
這是因?yàn)槲覀儾⒉皇窍胍苯訉⑦@個(gè)類顯示在MainActivity中。
而是要通過(guò)點(diǎn)擊不同的FruitListFragment的子項(xiàng)來(lái)動(dòng)態(tài)地生成FruitTitleAndDescFragment 片段。
也正是為了完成這個(gè)點(diǎn)擊事件的回調(diào)方法,我們才定義了Callback接口,并讓MainActivity必須實(shí)現(xiàn)這個(gè)接口:
public class MainActivity extends AppCompatActivity implements FruitListFragment.Callback {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
//實(shí)現(xiàn)Callback接口中定義的抽象方法
@Override
public void onItemClicked(Integer id) {
//創(chuàng)建一個(gè)Bundle,以向因?yàn)辄c(diǎn)擊事件而將要生成的Fragment中傳入?yún)?shù)
Bundle arg = new Bundle();
arg.putInt(FruitTitleAndDescFragment.ITEM_ID, id);
// 創(chuàng)建FruitTitleAndDescFragment對(duì)象
FruitTitleAndDescFragment fragment = new FruitTitleAndDescFragment();
// 向fragment中傳入?yún)?shù)
fragment.setArguments(arg);
// 用fragment替換fruit_desc_container中正在顯示的Fragment
android.app.FragmentManager fragmentManager = getFragmentManager();
android.app.FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.fruit_desc_container,fragment);
transaction.addToBackStack(null);
transaction.commit();
}
}
可以看到:
- 我們首先創(chuàng)建一個(gè)Bundle,以向因?yàn)辄c(diǎn)擊事件而將要生成的Fragment中傳入?yún)?shù);
- 然后調(diào)用Bundle類的putInt()方法向該Bundle對(duì)象中傳入數(shù)據(jù)。
可以看到putInt()方法接收的其實(shí)就是鍵值對(duì): String類型的鍵,int類型的值。
這里傳入的
arg.putInt(FruitTitleAndDescFragment.ITEM_ID, id);
FruitTitleAndDescFragment.ITEM_ID看上去好像很復(fù)雜,其實(shí)各位童鞋如果記性好的話,會(huì)發(fā)現(xiàn)這不過(guò)個(gè)定義在FruitTitleAndDescFragment類中的字符串常量:
public class FruitTitleAndDescFragment extends android.app.Fragment {
public static final String ITEM_ID = "item_id";
...
鍵值對(duì)的鍵有了,那這個(gè)int類型的id值又是打哪兒來(lái)呢?
還記得我們?cè)贔ruitListFragment中重寫的onListItemClick()方法嗎?
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
// 觸發(fā)myCallback的onItemclicked()方法
myCallback.onItemClicked(AllFruit.FRUIT_LIST_ITEMS
.get(position).id);
}
我們首先用Fruit類對(duì)象的list集合 AllFruit.FRUIT_LIST_ITEM 調(diào)用get()方法;
為get()方法傳入重寫onListItemClick()方法時(shí)傳入的position參數(shù);
這就得到了具體是哪一個(gè)Fruit子項(xiàng)被點(diǎn)擊,然后再得到該子項(xiàng)的id。
...
// 創(chuàng)建FruitTitleAndDescFragment對(duì)象
FruitTitleAndDescFragment fragment = new FruitTitleAndDescFragment();
// 向fragment中傳入?yún)?shù)
fragment.setArguments(arg);
// 用fragment替換fruit_desc_container中正在顯示的Fragment
android.app.FragmentManager fragmentManager = getFragmentManager();
android.app.FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.fruit_desc_container,fragment);
//將片段加入返回棧,也就是當(dāng)按下back鍵后,會(huì)顯示先前被replace掉的片段
transaction.addToBackStack(null);
transaction.commit();
準(zhǔn)備就緒之后,我們便創(chuàng)建一個(gè)新的FruitTitleAndDescFragment的對(duì)象。并調(diào)用setArguments(arg)方法為其指定了參數(shù),也就是那個(gè)帶著id信息的Bundle對(duì)象。
現(xiàn)在知道前面FruitTitleAndDescFragment類中onCreate()方法的作用了吧:
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments().containsKey(ITEM_ID)){
fruit = AllFruit.FRUIT_MAP_ITEMS
.get(getArguments().getInt(ITEM_ID));
}
}
只要是有新的FruitTitleAndDescFragment對(duì)象被Create,onCreate()方法就會(huì)執(zhí)行。
然后我們就能夠通過(guò)Bundle對(duì)象的getArguments()和getInt()方法接收到被點(diǎn)擊的水果對(duì)象的id信息。
最后再以該id為鍵在之前定義的水果對(duì)象的map集合中查到對(duì)應(yīng)的值。
另外,替換Fragment的操作其實(shí)很簡(jiǎn)單:
無(wú)非就是先得到FragmentManager,然后再由它創(chuàng)建FragmentTransaction。FragmentTransaction對(duì)象再調(diào)用replace方法替換和commit方法執(zhí)行就可以了。
唯一需要注意的就是replace()方法的參數(shù)。R.id.fruit_desc_container指的就是FrameLayout的id。也很簡(jiǎn)單,不再贅述。
當(dāng)然,最重要的也最精彩的部分還是Callback接口的創(chuàng)建。
其巧妙之處就在于:
- MainActivity實(shí)現(xiàn)Callback接口之后,它就可以被視作是一個(gè)Callback對(duì)象了。
- 所以MainActivity自然能調(diào)用接口中的onItemCliked()方法。
- 而這個(gè)所謂的MainActivity正是我們念叨了半天的會(huì)與被創(chuàng)建出來(lái)的FruitTitleAndDescFragment對(duì)象不斷產(chǎn)生和失去關(guān)聯(lián)的那個(gè)Activity。
- 所以我們?cè)趏nAttach()和onDetach()方法中都可以引用到這個(gè)Activity
// 當(dāng)Fragment被添加、顯示到Activity中時(shí),回調(diào)此方法
@Override
public void onAttach(Context context) {
super.onAttach(context);
// 如果Activity沒(méi)實(shí)現(xiàn)Callback接口,則拋出異常
if (!(context instanceof Callback)){
throw new IllegalStateException(
"FruitListFragment 所在的Activity必須實(shí)現(xiàn)Callback接口"
);
}
//把該Activity當(dāng)做Callback對(duì)象
myCallback = (Callback) context;
}//onAttach()結(jié)束
// 當(dāng)該Fragment從它所屬的Activity中被刪除時(shí)回調(diào)此方法
@Override
public void onDetach() {
super.onDetach();
// 給myCallback賦空值
myCallback = null;
}//onDetach()結(jié)束
- 而也正是因?yàn)槲覀冊(cè)趏nAttach()方法中通過(guò)
//把該Activity當(dāng)做Callback對(duì)象
myCallback = (Callback) context;
獲得了對(duì)MainActivity的引用,我們才得以在onListItemClick()方法中通過(guò)myCallback來(lái)調(diào)用接口中定義的onItemCliked()方法。
- 如此,我們才得以在每次點(diǎn)擊FruitListFragment列表中的子項(xiàng)時(shí)都能在MainActivity的FrameLayout布局部分生成一個(gè)新的FruitTitleAndDescFragment的實(shí)例。
- 看上去也就像是,點(diǎn)擊左邊列表中的水果名,就能在右邊列表顯示其相關(guān)的name和description信息。
--- The End
事無(wú)巨細(xì)地分析,不知不覺(jué)就有點(diǎn)長(zhǎng)篇累牘了。但愿能為各位同道中人填點(diǎn)坑吧!
水平有限,難免紕漏文中如有不當(dāng)之處歡迎批評(píng)指正。
諸君共勉:)