1. 程式人生 > >第一行程式碼——第三章:軟體也要拼臉蛋——UI開發的點點滴滴

第一行程式碼——第三章:軟體也要拼臉蛋——UI開發的點點滴滴

目錄:

3.1 如何編寫程式介面

3.2 常用控制元件的使用方法

3.2.1TextView

3.2.2 Button

3.2.3 EditText

3.2.4 ImageView

3.2.5 ProgressBar

3.2.6 AlertDialog

3.2.7 ProgressDialog

3.3 詳解4種常用佈局

3.3.1 線性佈局

3.3.2 相對佈局

3.3.3 幀佈局

3.3.4 百分比佈局

3.4 系統控制元件不夠用?建立自定義控制元件

3.4.1 引入佈局

3.4.2 建立自定義控制元件

3.5 最常用和最難用的控制元件——ListView

3.5.1 ListView的簡單用法

3.5.2 定製ListView的介面

3.5.3 提升ListView的執行效率

3.5.4 ListView的點選事件

3.6 更強大的滾動控制元件——RecyclerView

3.6.1 RecyclerView的基本用法

3.6.2 實現橫向滾動和瀑布流佈局

3.6.3 RecyclerView的點選事件

3.7 編寫介面的最佳實踐

3.7.1 製作Nine-Patch圖片

3.7.2 編寫精美的聊天佈局

3.8 小結與點評


知識點:

3.1 如何編寫程式介面

Android中有多種編寫程式介面的方式可供選擇。Android Studio 和Eclipse中都提供了相應的視覺化編輯器,允許使用拖放控制元件的方式來編寫佈局,並能在檢視上直接修改控制元件的屬性。不過我並不推薦你使用這種方式來編寫介面,因為視覺化編輯工具並不利於你去真正瞭解介面背後的實現原理。通過這種方式製作出的介面通常不具有很好的螢幕適配性,而且當需要編寫較為複雜的介面時,視覺化編輯工具將很難勝任。因此本書中所有的介面都將通過最基本的方式去實現,即編寫XML程式碼。等你完全掌握了使用XML來編寫介面的方法之後,不管是進行高複雜度的介面實現,還是分析和修改當前現有介面,對你來說都將是手到擒來。

3.2 常用控制元件的使用方法

3.2.1TextView

顯示一段文字資訊

   <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="HelloWord"
        />

3.2.2 Button

使用者互動的控制元件(比如,點選跳轉) 

<Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Im Button"
        />

3.2.3 EditText

輸入 和編輯內容(如:微信傳送的訊息)  

   <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="請輸入內容"
        />

3.2.4 ImageView

圖片控制元件  用於顯示圖片

<ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher"
        />

3.2.5 ProgressBar

 進度條

Style不寫 預設 圓形進度條 ?android:attr/progressBarStyleHorizontal

  <ProgressBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        style="?android:attr/progressBarStyleHorizontal"
        />

3.2.6 AlertDialog

可以在當前的介面彈出一個對話方塊

 new AlertDialog.Builder(this)
                .setTitle("確定要刪除嗎?")
                .setMessage("Something important.")
                .setPositiveButton("確定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {

                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {

                    }
                })
                .show();

3.2.7 ProgressDialog

進度條彈框

 final ProgressDialog dialog = new ProgressDialog(this);
        dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);// 設定水平進度條
        dialog.setCancelable(true);// 設定是否可以通過點選Back鍵取消
        dialog.setCanceledOnTouchOutside(false);// 設定在點選Dialog外是否取消Dialog進度條
        dialog.setIcon(R.drawable.ic_launcher);// 設定提示的title的圖示,預設是沒有的
        dialog.setTitle("提示");
        dialog.setMax(100);
        dialog.setButton(DialogInterface.BUTTON_POSITIVE, "確定",
                new DialogInterface.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // TODO Auto-generated method stub

                    }
                });
        dialog.setButton(DialogInterface.BUTTON_NEGATIVE, "取消",
                new DialogInterface.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // TODO Auto-generated method stub

                    }
                });
        dialog.setButton(DialogInterface.BUTTON_NEUTRAL, "中立",
                new DialogInterface.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // TODO Auto-generated method stub

                    }
                });
        dialog.setMessage("這是一個水平進度條");
        dialog.show();
        new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                int i = 0;
                while (i < 100) {
                    try {
                        Thread.sleep(200);
                        // 更新進度條的進度,可以在子執行緒中更新進度條進度
                        dialog.incrementProgressBy(1);
                        // dialog.incrementSecondaryProgressBy(10)//二級進度條更新方式
                        i++;

                    } catch (Exception e) {
                        // TODO: handle exception
                    }
                }
                // 在進度條走完時刪除Dialog
                dialog.dismiss();

            }
        }).start();

3.3 詳解4種常用佈局

3.3.1 線性佈局

LinearLayout又稱作線性佈局,是一種非常常用的佈局。

android:orientation="horizontal" 預設水平    vertical  修改後橫向 

 <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">


    </LinearLayout>

3.3.2 相對佈局

可以通過相對定位的方式讓空間出現在佈局的任何位置。

如下讓Button2 在Button1 右側

 <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" 
            android:id="@+id/btn_1"
            />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/btn_2"
            android:layout_toRightOf="@+id/btn_1"
            />

    </RelativeLayout>

3.3.3 幀佈局

應用場景也少了很多,所有控制元件都會顯示在佈局的左上角

<FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/btn_1"
            />

    </FrameLayout>

3.3.4 百分比佈局

前面3 種佈局都是從Android 1.0版本中就開始支援了,一直沿用到現在,但是特殊情況以上佈局實現起來很不支援的

百分比庫 地址 使用方式 請同時檢視
https://github.com/JulienGenoud/android-percent-support-lib-sample

3.4 系統控制元件不夠用?建立自定義控制元件

當系統自帶的控制元件不能支援我們的需求時,就需要自定義空控制元件了。

3.4.1 引入佈局

3.4.2 建立自定義控制元件

這2章主要是講 新建一個xml檔案 設定標題欄的佈局

並不是我們所想的自定義View

3.5 最常用和最難用的控制元件——ListView

由於螢幕控制元件比較有限,能夠一次性在螢幕上顯示的內容不多,所以有大資料的時候需要用到列表

3.5.1 ListView的簡單用法

在佈局中加入ListView 控制元件,併為ListView 指定了一個id 設定成match_parent 佔滿整個空間

  <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/list_view"
        >
    </ListView>

在Activity中

public class MainActivity extends Activity {  
private String[] data = { "Apple", "Banana", "Orange", "Watermelon",  
"Pear", "Grape", "Pineapple", "Strawberry", "Cherry", "Mango" };  
@Override  
protected void onCreate(Bundle savedInstanceState) {  
super.onCreate(savedInstanceState);  
setContentView(R.layout.activity_main);  
ArrayAdapter<String> adapter = new ArrayAdapter<String>(  
MainActivity.this, android.R.layout.simple_list_item_1, data);  
ListView listView = (ListView) findViewById(R.id.list_view);  
listView.setAdapter(adapter);  
}  
}  

在這裡我運用了系統包含的一個TextView的佈局檔案:android.R.layout.simple_expandable_list_item_1,呼叫這個比較方便,

ArrayAdapter<String> adapter = new ArrayAdapter<String>(  MainActivity.this, android.R.layout.simple_list_item_1, data);  的意思是:建立一個數組介面卡的程式碼,裡面有三個引數,第一個引數是上下文,就是當前的Activity, 第二個引數是android sdk中自己內建的一個佈局,它裡面只有一個TextView,這個引數是表明我們陣列中每一條資料的佈局是這個view,就是將每一條資料都顯示在這個 view上面;第三個引數就是我們要顯示的資料。listView會根據這三個引數,遍歷data裡面的每一條資料,讀出一條,顯示到第二 個引數對應的佈局中,這樣就形成了我們看到的listView.

ArrayAdapter是BaseAdapter的子類

3.5.2 定製ListView的介面

定義一個實體類Fruit


public class Fruit {
    private String name;
    private int imageId;
 
    public Fruit(String name, int imageId) {
        this.name = name;
        this.imageId = imageId;
    }
 
    public String getName() {
        return name;
    }
 
    public int getImageId() {
        return imageId;
    }
}

為 ListView 的子項指定一個我們自定義的佈局 fruit_item.xml。

<?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">
    <ImageView
        android:id="@+id/fruit_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
 
    <TextView
        android:id="@+id/fruit_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginLeft="10dip" />
</LinearLayout>

建立一個自定義的介面卡 FruitAdapter,這個介面卡繼承自 ArrayAdapter。重寫構造方法和 getView 方法。


 
public class FruitAdapter extends ArrayAdapter{
    private final int resourceId;
 
    public FruitAdapter(Context context, int textViewResourceId, List<Fruit> objects) {
        super(context, textViewResourceId, objects);
        resourceId = textViewResourceId;
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Fruit fruit = (Fruit) getItem(position); // 獲取當前項的Fruit例項
        View view = LayoutInflater.from(getContext()).inflate(resourceId, null);//例項化一個物件
        ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);//獲取該佈局內的圖片檢視
        TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);//獲取該佈局內的文字檢視
        fruitImage.setImageResource(fruit.getImageId());//為圖片檢視設定圖片資源
        fruitName.setText(fruit.getName());//為文字檢視設定文字內容
        return view;
    }
}     

View view = LayoutInflater.from(getContext()).inflate(resourceId, null);使用Inflater物件來將佈局檔案解析成一個View

在MainActivity中編寫,初始化水果資料


 
public class MainActivity extends Activity {
    private List<Fruit> fruitList = new ArrayList<Fruit>();
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits(); // 初始化水果資料
        FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, fruitList);
        ListView listView = (ListView) findViewById(R.id.list_view);
        listView.setAdapter(adapter);
    }
 
    private void initFruits() {
        Fruit apple = new Fruit("Apple", R.drawable.apple_pic);
        fruitList.add(apple);
        Fruit banana = new Fruit("Banana", R.drawable.banana_pic);
        fruitList.add(banana);
        Fruit orange = new Fruit("Orange", R.drawable.orange_pic);
        fruitList.add(orange);
        Fruit watermelon = new Fruit("Watermelon", R.drawable.watermelon_pic);
        fruitList.add(watermelon);
        Fruit pear = new Fruit("Pear", R.drawable.pear_pic);
        fruitList.add(pear);
        Fruit grape = new Fruit("Grape", R.drawable.grape_pic);
        fruitList.add(grape);
        Fruit pineapple = new Fruit("Pineapple", R.drawable.pineapple_pic);
        fruitList.add(pineapple);
        Fruit strawberry = new Fruit("Strawberry", R.drawable.strawberry_pic);
        fruitList.add(strawberry);
        Fruit cherry = new Fruit("Cherry", R.drawable.cherry_pic);
        fruitList.add(cherry);
        Fruit mango = new Fruit("Mango", R.drawable.mango_pic);
        fruitList.add(mango);
    }
}

執行效果圖

這樣一個簡單的ListView介面就完成了

3.5.3 提升ListView的執行效率

  •  新增加一個內部類ViewHolder,對控制元件進行快取。
  • 複用convertView
  • 減少findViewById的次數。

3.5.4 ListView的點選事件

以下為單個item點選事件

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
          @Override
          public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
              
          }
      });

如果要實現子view點選事件 可以寫介面實現

3.6 更強大的滾動控制元件——RecyclerView

增加版的ListView優化Listview中的各種問題,擴充套件性要好。

3.6.1 RecyclerView的基本用法

沒有新增build gradle 的 新增依賴庫 

版本號 可改

implementation 'com.android.support:recyclerview-v7:27.1.1'

新增RecyclerView

  <android.support.v7.widget.RecyclerView
                android:id="@+id/recycler_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />

同上使用水果類就可以

新建Adapter 

我這裡用了ButterKnife 註解

package com.dak.administrator.firstcode.material_design;

import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.dak.administrator.firstcode.R;

import java.util.List;

import butterknife.BindView;
import butterknife.ButterKnife;

/**
 * Created by Administrator on 2018/11/7.
 */

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {

    private Context mContext;

    private List<Fruit> mFruitList;

    public FruitAdapter(List<Fruit> fruitList) {
        mFruitList = fruitList;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (mContext == null) {
            mContext = parent.getContext();
        }

        View view = LayoutInflater.from(mContext).inflate(R.layout.fruit_item, parent, false);
        final ViewHolder holder = new ViewHolder(view);
        holder.cardView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                int postion = holder.getAdapterPosition();
                Fruit fruit = mFruitList.get(postion);
                Intent intent = new Intent(mContext, CollapseActivity.class);
                intent.putExtra(CollapseActivity.FRUIT_NAME, fruit.getName());
                intent.putExtra(CollapseActivity.FRUIT_IMAGE_ID, fruit.getImageId());
                mContext.startActivity(intent);

            }
        });
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        Fruit fruit = mFruitList.get(position);

        holder.fruitName.setText(fruit.getName());
        Glide.with(mContext).load(fruit.getImageId()).into(holder.fruitImage);

    }

    @Override
    public int getItemCount() {
        return mFruitList.size();
    }


    static class ViewHolder extends RecyclerView.ViewHolder{
        @BindView(R.id.fruit_image)
        ImageView fruitImage;
        @BindView(R.id.fruit_name)
        TextView fruitName;
        @BindView(R.id.card_view)
        CardView cardView;

        public ViewHolder(View view) {
            super(view);
            ButterKnife.bind(this, view);
        }
    }
}

Activity呼叫

 adapter = new FruitAdapter(fruitsList);
        GridLayoutManager layoutManager = new GridLayoutManager(this, 2);
        recyclerView.setLayoutManager(layoutManager);
        recyclerView.setAdapter(adapter);

3.6.2 實現橫向滾動和瀑布流佈局

橫向滾動只要把 之前GridLayoutManager 改成如下即可 

LinearLayoutManager預設 豎向

LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);

瀑布流:

StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);

當然瀑布流是存在著許多坑 請看

https://www.jianshu.com/p/b0f80b1c29d0

https://blog.csdn.net/lhk147852369/article/details/84346240

 

3.6.3 RecyclerView的點選事件

點選事件已經在上程式碼加上了 在ViewHolder當中 這是在adapter的
如果需要在Activity或其他地方 可以寫介面實現,這裡不說了。

http://www.jcodecraeer.com/plus/view.php?aid=7881

3.7 編寫介面的最佳實踐

3.7.1 製作Nine-Patch圖片

https://blog.csdn.net/lhk147852369/article/details/84346031

3.7.2 編寫精美的聊天佈局

主要是在onBindViewHolder 判斷傳過來的資訊是傳送的還是接收的

通過Type值 去設定Visible 或者Gone 

當然還有另外方法

通過getItemViewType方法

https://www.cnblogs.com/android-blogs/p/5690853.html

3.8 小結與點評

郭霖總結:

雖然本章的內容很多,但我覺得學習起來應該還是挺愉快的吧。不同於上一章中我們來來回回使用那幾個按鈕,本章可以說是使用了各種各樣的控制元件,製作出了豐富多彩的介面。尤其是在實戰環節,編寫出了那麼精美的聊天介面,你的滿足感應該比上一章還要強吧?

本章從Android中的一些常見控制元件開始人手,依次介紹了基本佈局的用法、自定義控制元件的方法、ListView 的詳細用法以及RecyclerView的使用,基本已經將重要的UI知識點全部覆蓋了。想想在開始的時候我說不推薦使用視覺化的編輯工具,而是應該全部使用XML的方式來編寫介面,現在你是不是已經感覺使用XML非常簡單了呢?以後不管面對多麼複雜的介面,我希望你都能夠自信滿滿,因為真正理解了介面編寫的原理之後,是沒有什麼能夠難得倒你的。
不過到目前為止,我們還只是學習了Android 手機方面的開發技巧,下一章將會涉及一些Android平板方面的知識點,能夠同時相容手機和平板也是自Android4.0 系統開始就支援的特性。適當地放鬆和休息一段時間後, 我們再來繼續前行吧!

我的總結:

這章還是比較重要的,因為在工作中接觸到的很多都是和列表有關的。