Material Design的基本概念
Material Design是Google設計的一套視覺語言,將優先的經典的設計原理與科技創新相結合,爲開發者提供一套完成視覺和交互設計規範。移動設備是這套設計語言的基礎對象,讓用戶在不同的平臺、不同尺寸的設備上能保持一致的體驗。
Material Design強調交互上的即時反饋,即對於用戶的觸控等行爲app需要給出即時的反應。同時Material Design要求應用給用戶帶入感,讓用戶在使用時是沉浸在當前的應用當中。例如Google給出了沉浸式狀態欄等“工具”,希望通過改變StatusBar和NavigationBar來給用戶更強的融入感,專注於應用本身提供的內容。
Google從動畫、顏色、樣式、觸控反饋、佈局等多個方面給出了Material Design的設計要求。無論是單一的控件還是圖文佈局,Google都給出了明確的設計說明,有興趣的同學可以去上方提到的官方鏈接處做進一步瞭解。
RecyclerView的使用
寫條目佈局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tv_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
寫Adapter以及其內部類自定義的ViewHolder:
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.MyViewHolder> {
private List<String> mDatas;
private Context mContext;
public MyRecyclerViewAdapter(Context context, List<String> datas) {
mContext = context;
mDatas = datas;
}
class MyViewHolder extends RecyclerView.ViewHolder {
TextView tv_item;
MyViewHolder(View itemView) {
super(itemView);
tv_item = (TextView) itemView.findViewById(R.id.tv_item);
}
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = View.inflate(parent.getContext(), R.layout.item_list, null);
return new MyViewHolder(itemView);
}
@Override
public void onBindViewHolder(MyViewHolder holder, final int position) {
holder.tv_item.setText(mDatas.get(position));
holder.tv_item.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mContext, mDatas.get(position), Toast.LENGTH_SHORT).show();
}
});
}
@Override
public int getItemCount() {
return mDatas.size();
}
}
- 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
- 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
在Activity中的使用,通過設置不同的LayoutManager就可以實現不同的佈局效果:
public class MDRecyclerViewActivity extends AppCompatActivity {
private RecyclerView rv_list;
private MyRecyclerViewAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_md_recyclerview);
rv_list = (RecyclerView) findViewById(R.id.rv_list);
List<String> datas = new ArrayList<>();
for (int i = 0; i < 100; i++) {
datas.add("第" + i + "個數據");
}
mAdapter = new MyRecyclerViewAdapter(this, datas);
rv_list.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));
rv_list.setAdapter(mAdapter);
}
}
- 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
- 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
Inflate時的注意事項:
在Adapter中的onCreateViewHolder,需要Inflate佈局文件,有三種寫法:
View itemView = View.inflate(parent.getContext(), R.layout.item_list, null)
View itemView = View.inflate(parent.getContext(), R.layout.item_list, parent)
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list, parent, false)
寫法一般情況下是沒有問題的,但是當我們在onBindViewHolder中拿到佈局中TextView的LayoutParams的時候,就有可能返回空。
寫法二直接Crash,因爲ItemView佈局已經有一個Parent了(Inflate的時候把ItemView添加到Recycleview了),不能再添加一個Parent(Recycleview再次添加ItemView)。
寫法三是一、二的兩種兼容方案,推薦這種寫法。
添加增刪接口
在Adapter中添加以及刪除的接口:
public void addItem(String data, int position) {
mDatas.add(position, data);
notifyItemInserted(position);
}
public void removeItem(int position) {
mDatas.remove(position);
notifyItemRemoved(position);
}
注意如果你想使用RecyclerView提供的增刪動畫,那麼就需要使用新增的notify方法。
添加條目點擊監聽
自定義一個點擊回調接口:
ItemClickListener mItemClickListener;
public interface ItemClickListener {
void onclick(int position, String data);
}
public void setItemClickListener(ItemClickListener listener) {
mItemClickListener = listener;
}
public abstract class ItemClickListenerPosition implements View.OnClickListener {
private int mPosition;
public ItemClickListenerPosition(int position) {
mPosition = position;
}
public int getPosition() {
return mPosition;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
ItemClickListenerPosition是一個自定義的OnClickListener,目的就是爲了把Position和監聽綁定在一起,同時也使用了getLayoutPosition方法。防止了點擊Position錯亂的問題。
(onBindViewHolder() 方法中的位置參數 position 不是實時更新的,例如在我們刪除元素後,item 的 position 並沒有改變。)
然後在onBindViewHolder裏面進行監聽:
@Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
holder.itemView.setOnClickListener(new ItemClickListenerPosition(holder.getLayoutPosition()) {
@Override
public void onClick(View v) {
if (mItemClickListener != null) {
mItemClickListener.onclick(getPosition(), mDatas.get(getPosition()));
}
}
});
}
想詳細瞭解RecyclerView的使用,請參考《 一篇博客理解Recyclerview的使用》
DrawerLayout+NavigationView
使用DrawerLayout實現側滑:
定義一個佈局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#D197F2"
app:title="我是標題"
app:titleTextColor="#fff"/>
<android.support.v4.widget.DrawerLayout
android:id="@+id/drawer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="內容"/>
</LinearLayout>
<LinearLayout
android:layout_width="200dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="@android:color/holo_blue_light"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="側滑菜單1"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="側滑菜單2"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="側滑菜單3"/>
</LinearLayout>
</android.support.v4.widget.DrawerLayout>
</LinearLayout>
- 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
- 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
這個佈局側滑菜單包括了菜單部分以及內容部分,用DrawerLayout來包裹起來。其中,菜單部分的根佈局需要添加Android:layout_gravity=”start”,如果是右滑的話,改爲end即可。
這樣就可以完成了一個基本的側滑效果。
DrawerLayout的實現其實是通過ViewDragHelper來實現的,DrawerLayout構造函數的相關代碼如下:
public DrawerLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mLeftCallback = new ViewDragCallback(Gravity.LEFT);
mRightCallback = new ViewDragCallback(Gravity.RIGHT);
mLeftDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mLeftCallback);
mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
mLeftDragger.setMinVelocity(minVel);
mLeftCallback.setDragger(mLeftDragger);
}
利用DrawerLayout的監聽實現一些效果
例如,我們可以實現側滑的時候,Toolbar左上角的按鈕實時變化,我們可以添加一個監聽ActionBarDrawerToggle:
toolbar = (Toolbar) findViewById(R.id.toolbar)
drawer = (DrawerLayout) findViewById(R.id.drawer)
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.drawer_open, R.string.drawer_close)
toggle.syncState()
drawer.addDrawerListener(toggle)
分析一下實現原理:
其中,ActionBarDrawerToggle實現了DrawerLayout.DrawerListener。並且在滑動的過程中不斷 刷新左上角的Drawerable:
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
setPosition(Math.min(1f, Math.max(0, slideOffset)));
}
setPosition的實現如下:
private void setPosition(float position) {
if (position == 1f) {
mSlider.setVerticalMirror(true);
} else if (position == 0f) {
mSlider.setVerticalMirror(false);
}
mSlider.setProgress(position);
}
其實就是在滑動的過程中不斷改變mSlider(一個自定義Drawerable對象)的Progress,從而不斷刷新狀態。
因此,我們可以做一些自定義的特效,例如側滑的時候縮放、平移:
drawer.addDrawerListener(new DrawerLayout.DrawerListener() {
@Override
public void onDrawerStateChanged(int newState) {
}
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
View content = drawer.getChildAt(0);
float scale = 1 - slideOffset;
float leftScale = (float) (1 - 0.3 * scale);
float rightScale = (float) (0.7f + 0.3 * scale);
drawerView.setScaleX(leftScale);
drawerView.setScaleY(leftScale);
content.setScaleX(rightScale);
content.setScaleY(rightScale);
content.setTranslationX(drawerView.getMeasuredWidth() * (1 - scale));
}
@Override
public void onDrawerOpened(View drawerView) {
}
@Override
public void onDrawerClosed(View drawerView) {
}
});
- 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
- 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
DrawerLayout+NavigationView實現側滑
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/fl"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/navigation_headerlayout"
app:menu="@menu/navigation_menu"
/>
</android.support.v4.widget.DrawerLayout>
- 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
- 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
我們指定了頭部如下:
<?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:gravity="center_horizontal"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_icon"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_marginTop="20dp"
android:src="@drawable/icon_people"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="璐寶寶"
android:textSize="20sp"/>
</LinearLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
菜單部分如下(menu文件夾下建立),其中菜單可以嵌套:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_gallery"
android:icon="@android:drawable/ic_menu_gallery"
android:orderInCategory="100"
android:title="相冊"
/>
<item
android:id="@+id/action_details"
android:icon="@android:drawable/ic_menu_info_details"
android:orderInCategory="100"
android:title="詳情"
/>
<item
android:id="@+id/action_about"
android:icon="@android:drawable/ic_menu_help"
android:orderInCategory="100"
android:title="關於"
/>
<item
android:id="@+id/action_music"
android:icon="@android:drawable/ic_menu_more"
android:orderInCategory="100"
android:title="音樂"
>
<menu>
<item
android:id="@+id/action_play"
android:icon="@android:drawable/ic_media_play"
android:title="播放"/>
<item
android:id="@+id/action_pause"
android:icon="@android:drawable/ic_media_pause"
android:title="暫停"/>
</menu>
</item>
</menu>
- 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
- 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
到現在爲止,就可以實現側滑了,最後我們添加上對應的點擊事件,然後關閉菜單:
nav_view = (NavigationView) findViewById(R.id.nav_view)
drawer = (DrawerLayout) findViewById(R.id.drawer)
nav_view.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
Toast.makeText(NavigationViewActivity.this, item.getTitle(), Toast.LENGTH_SHORT).show()
drawer.closeDrawer(nav_view)
return false
}
})
nav_view.getHeaderView(0).findViewById(R.id.iv_icon).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(NavigationViewActivity.this, "點擊了頭部的圖標", Toast.LENGTH_SHORT).show()
drawer.closeDrawer(nav_view)
}
})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
SnackBar
Snackbar snackbar = Snackbar.make(v, "是否打開XXX模式", Snackbar.LENGTH_SHORT);
snackbar.setAction("打開", new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "打開XXX模式");
}
});
snackbar.setCallback(new Snackbar.Callback() {
@Override
public void onShown(Snackbar snackbar) {
super.onShown(snackbar);
Log.e(TAG, "顯示");
}
@Override
public void onDismissed(Snackbar snackbar, int event) {
super.onDismissed(snackbar, event);
Log.e(TAG, "關閉");
}
});
snackbar.show();
- 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
- 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
Snackbar的Duration有三種:
Snackbar.LENGTH_SHORT
Snackbar.LENGTH_LONG
Snackbar.LENGTH_INDEFINITE—無限長
make方法傳入的是一個錨點,這裏傳入了一個Button對象。然後還可以設置動作以及回調監聽。
Snackbar的詳細使用參見《輕量級控件SnackBar使用以及源碼分析》
TextInputLayout
佈局:
<android.support.design.widget.TextInputLayout
android:id="@+id/til_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:hintAnimationEnabled="true">
<EditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="請輸入用戶名"/>
</android.support.design.widget.TextInputLayout>
hintAnimationEnabled屬性是設置是否開啓Hint的動畫。
需要注意的是,TextInputLayout必須包含一個EditText。
下面是一個基本的例子:
public class TextInputMainActivity extends AppCompatActivity {
private TextInputLayout til_input;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_text_input);
til_input = (TextInputLayout) findViewById(R.id.til_input);
til_input.getEditText().addTextChangedListener(new MaxTextTextWatcher(til_input, "字數不能大於6", 6));
til_input.setCounterEnabled(true);
til_input.setCounterMaxLength(6);
}
class MaxTextTextWatcher implements TextWatcher {
private TextInputLayout mTextInputLayout;
private String mErrorString;
private int maxTextCount;
public MaxTextTextWatcher(TextInputLayout textInputLayout, String errorString, int maxTextCount) {
mTextInputLayout = textInputLayout;
mErrorString = errorString;
this.maxTextCount = maxTextCount;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
String str = mTextInputLayout.getEditText().getText().toString().trim();
if (!TextUtils.isEmpty(str)) {
if (str.length() > maxTextCount) {
mTextInputLayout.setError(mErrorString);
mTextInputLayout.setErrorEnabled(true);
} else {
mTextInputLayout.setErrorEnabled(false);
}
}
}
}
}
- 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
- 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
在這個例子裏面,我們利用了TextInputLayout的錯誤提示、字數統計功能,基本的使用都比較簡單。
在TextInputLayout可以輕鬆地通過getEditText方法找到它所包裹的EditText。、
在顯示錯誤的時候,需要先設置錯誤的提示,每次顯示的時候都要設置。
大部分屬性都可以通過xml的方式設置,這裏通過代碼動態設置只是爲了方便演示。
TextInputLayout詳細使用請參見強大的提示控件TextInputLayout使用以及源碼分析
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
app:logo="@drawable/ic_launcher"
app:subtitle="子標題"
app:navigationIcon="@drawable/abc_ic_ab_back_mtrl_am_alpha"
app:subtitleTextColor="#fff"
app:title="我是標題"
app:titleTextColor="#fff"></android.support.v7.widget.Toolbar>
Toolbar是一個ViewGroup,裏面可以放子控件。因此,如果你想標題居中的話,那麼就放入一個TextView吧。
這裏的?attr/colorPrimary是使用了系統的顏色值,當然我們也可以在主題中重寫。
注意:Toolbar需要使用Appcompat的一套東西。
返回監聽:
toolbar.setNavigationOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
實現Toolbar隨着界面滑動透明度變化效果
首先我們需要一個佈局,通過相對佈局把Toolbar壓在ScrollView(或者ListView、RecyclerView)的上面。Toolbar的高度與ScrollView上方內邊距都使用系統的actionBarSize。
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.nan.advancedui.toolbar.MyScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingTop="?attr/actionBarSize">
<!--這裏是我們的內容佈局-->
</com.nan.advancedui.toolbar.MyScrollView>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:title="標題"
>
</android.support.v7.widget.Toolbar>
</RelativeLayout>
- 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
- 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
還需要注意給ScrollView設置多兩個屬性,不然的話滑出去以後上內邊距會一直保留:
android:clipToPadding=”false” 該控件的繪製範圍是否不在Padding裏面。false:繪製的時候範圍會考慮padding即會往裏面縮進。
android:clipChildren=”false” 子控件是否能不超出padding的區域(比如ScrollView上滑動的時候,child可以滑出該區域)
然後監聽滑動事件,這裏如果是ScrollView的話,需要自定義重寫方法才能監聽:
public class MyScrollView extends ScrollView {
private OnAlphaListener listener;
public void setOnAlphaListener(OnAlphaListener listener) {
this.listener = listener;
}
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (listener != null) {
int scrollY = getScrollY();
int screen_height = getContext().getResources().getDisplayMetrics().heightPixels;
if (scrollY <= screen_height / 3f) {
listener.onAlpha(1 - scrollY / (screen_height / 3f));
}
}
}
}
- 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
- 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
透明度的計算需要根據實際情況來
自定義一個接口回調,Activity(Fragment)實:
public interface OnAlphaListener {
void onAlpha(float alpha);
}
界面的邏輯如下:
public class ToolbarActivity extends AppCompatActivity implements OnAlphaListener {
private Toolbar mToolbar;
private MyScrollView mScrollview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_toolbar);
mToolbar = (Toolbar) findViewById(R.id.toolbar);
mScrollview = (MyScrollView) findViewById(R.id.scrollView);
mScrollview.setOnAlphaListener(this);
}
@Override
public void onAlpha(float alpha) {
mToolbar.setAlpha(alpha);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
SearchView
SearchView也是V7包的控件,一般也是跟Toolbar中的菜單結合使用。
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.ricky.materialdesign.toolbar.MainActivity"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_search"
android:orderInCategory="100"
app:actionViewClass="android.support.v7.widget.SearchView"
app:showAsAction="always"
android:title="查找"/>
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
app:showAsAction="never"
android:title="設置"/>
<item
android:id="@+id/action_share"
android:orderInCategory="100"
app:showAsAction="always"
android:title="分享"
android:icon="@android:drawable/ic_menu_share"/>
<item
android:id="@+id/action_edit"
android:orderInCategory="100"
app:showAsAction="ifRoom"
android:title="編輯"
android:icon="@android:drawable/ic_menu_edit"/>
</menu>
- 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
- 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
這裏app:actionViewClass=”android.support.v7.widget.SearchView”是指定了菜單的View是一個SearchView。因此我們就可以在代碼中使用了:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
MenuItem item = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) MenuItemCompat.getActionView(item);
searchView.setIconified(false);
ImageView icon = (ImageView) searchView.findViewById(R.id.search_go_btn);
icon.setImageResource(R.drawable.abc_ic_voice_search_api_mtrl_alpha);
icon.setVisibility(View.VISIBLE);
searchView.setMaxWidth(200);
SearchView.SearchAutoComplete et = (SearchView.SearchAutoComplete) searchView.findViewById(R.id.search_src_text);
et.setHint("輸入商品名或首字母");
et.setHintTextColor(Color.WHITE);
searchView.setSubmitButtonEnabled(true);
icon.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "提交", 1).show();
}
});
searchView.setOnQueryTextFocusChangeListener(new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
}
});
searchView.setOnCloseListener(new OnCloseListener() {
@Override
public boolean onClose() {
return false;
}
});
searchView.setOnSearchClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "提交", 0).show();
}
});
searchView.setOnQueryTextListener(new OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String text) {
Toast.makeText(MainActivity.this, "提交文本:"+text, 0).show();
return false;
}
@Override
public boolean onQueryTextChange(String text) {
System.out.println("文本變化~~~~~"+text);
return false;
}
});
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
- 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
- 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
TabLayout
下面以TabLayout+ViewPager+Fragment爲例,講述TabLayout的基本使用。
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.TabLayout
android:id="@+id/tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabGravity="center"
app:tabIndicatorColor="#4ce91c"
app:tabMode="scrollable"
app:tabSelectedTextColor="#4ce91c"
app:tabTextColor="#ccc"
app:tabIndicatorHeight="5dp"
/>
<android.support.v4.view.ViewPager
android:id="@+id/vp"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</LinearLayout>
- 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
- 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
其中,需要關注的屬性有:
app:tabIndicatorColor="@color/colorPrimary_pink"
app:tabTextColor="@color/colorPrimary_pink"
app:tabSelectedTextColor="@color/colorPrimary_pinkDark"
app:tabMode="fixed"
app:tabGravity="center"
需要切換的Fragment,爲了方便,我們重用一個Fragment:
public class NewsDetailFragment extends Fragment {
@Override
@Nullable
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
TextView tv = new TextView(getContext());
Bundle bundle = getArguments();
String title = bundle.getString("title");
tv.setBackgroundColor(Color.rgb((int)(Math.random()*255), (int)(Math.random()*255), (int)(Math.random()*255)));
tv.setText(title);
return tv;
}
}
Activity的代碼:
public class TabLayoutActivity extends AppCompatActivity {
private TabLayout tabLayout;
private String[] title = {
"頭條",
"新聞",
"娛樂",
"體育",
"科技",
"美女",
"財經",
"汽車",
"房子",
"頭條"
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tab_layout);
final ViewPager viewPager = (ViewPager) findViewById(R.id.vp);
tabLayout = (TabLayout) findViewById(R.id.tablayout);
MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());
tabLayout.setupWithViewPager(viewPager);
viewPager.setAdapter(adapter);
setIndicator(this, tabLayout, 15, 15);
}
class MyPagerAdapter extends FragmentPagerAdapter {
public MyPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public CharSequence getPageTitle(int position) {
return title[position];
}
@Override
public Fragment getItem(int position) {
Fragment f = new NewsDetailFragment();
Bundle bundle = new Bundle();
bundle.putString("title", title[position]);
f.setArguments(bundle);
return f;
}
@Override
public int getCount() {
return title.length;
}
}
public static void setIndicator(Context context, TabLayout tabs, int leftDip, int rightDip) {
Class<?> tabLayout = tabs.getClass();
Field tabStrip = null;
try {
tabStrip = tabLayout.getDeclaredField("mTabStrip");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
tabStrip.setAccessible(true);
LinearLayout ll_tab = null;
try {
ll_tab = (LinearLayout) tabStrip.get(tabs);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
int left = (int) (getDisplayMetrics(context).density * leftDip);
int right = (int) (getDisplayMetrics(context).density * rightDip);
for (int i = 0; i < ll_tab.getChildCount(); i++) {
View child = ll_tab.getChildAt(i);
child.setPadding(0, 0, 0, 0);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1);
params.leftMargin = left;
params.rightMargin = right;
child.setLayoutParams(params);
child.invalidate();
}
}
public static DisplayMetrics getDisplayMetrics(Context context) {
DisplayMetrics metric = new DisplayMetrics();
((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(metric);
return metric;
}
public static float getPXfromDP(float value, Context context) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value,
context.getResources().getDisplayMetrics());
}
}
- 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
- 130
- 131
- 132
- 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
- 130
- 131
- 132
新提供的tabLayout.setupWithViewPager(viewPager);方法代替了註釋中的3個方法了,其實內部做的事都是一樣的。TabLayout默認沒有提供修改Indicator寬度的函數,需要我們通過反射的方式去設置。
用TabLayout實現底部導航(相對於傳統的TabHost,它是可滑動的)
只需要三個步驟:
1.在佈局中就把TabLayout放在佈局底部
2。去掉底部的indicator,app:tabIndicatorHeight=”0dp”
3.實現自己的效果,自定義的標籤佈局
代碼如下:
for (int i = 0; i < tabLayout.getTabCount(); i++) {
TabLayout.Tab tab = tabLayout.getTabAt(i);
tab.setCustomView(view);
}
CardView
CardView就是一個ViewGroup,裏面可以放置子佈局
<android.support.v7.widget.CardView
android:layout_width="300dp"
android:layout_height="200dp"
android:layout_margin="16dp"
android:clickable="true"
android:foreground="?attr/selectableItemBackground"
android:stateListAnimator="@drawable/z_translation"
app:cardCornerRadius="10dp"
app:cardElevation="10dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/test"/>
</android.support.v7.widget.CardView>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
其中,cardElevation是設置高度,高度越高,陰影越明顯。foreground屬性是設置點擊水波紋效果。cardCornerRadius是設置圓角的大小。stateListAnimator是設置點擊的動畫效果,點擊以後,往下壓,z_translation如下:
<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="true">
<objectAnimator
android:duration="@android:integer/config_shortAnimTime"
android:propertyName="translationZ"
android:valueTo="-15dp"
android:valueType="floatType"/>
</item>
<item>
<objectAnimator
android:duration="@android:integer/config_shortAnimTime"
android:propertyName="translationZ"
android:valueTo="0dp"
android:valueType="floatType"/>
</item>
</selector>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
CardView兼容性開發
創建layout、layout-v21兩套佈局,根據下面的差別寫兩份CardView的佈局文件。其中尤其注意的是stateListAnimator這個屬性,如果最小SDK版本低於21,AS就會警告。
1.陰影的細微差別
5.x系統:邊距陰影比較小,需要手動添加邊距16dp,android:layout_margin=”16dp”
4.x系統:邊距陰影比較大,手動修改邊距0dp(原因:兼容包裏面設置陰影效果自動設置了margin來處理16dp)
2.圓角效果的細微差別
5.x系統:圖片和佈局都可以很好的呈現圓角效果,圖片也變圓角了,因此5.x上面不需要設置app:contentPadding
4.x系統:圖不能變成圓角(圖片的直角會頂到CardView的邊上),如果要做成5.x一樣的效果:通過加載圖片的時候自己去處理成圓角(與CardView的圓角大小一樣),因此4.x上面不需要設置app:contentPadding,從而儘量好看一些
3.水波紋效果的差別
5.x系統:可以通過
android:foreground=”?attr/selectableItemBackground”實現
4.x系統:需要自己實現
4.點擊動畫的差別
5.x系統:可以通過android:stateListAnimator=”@drawable/z_translation”設置動畫
4.x系統:不能設置上述的動畫,因爲4.x沒有z軸的概念
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="right|bottom"
android:onClick="rotate"
android:src="@drawable/ic_add_white_24dp"
app:backgroundTint="?attr/colorPrimary"
app:elevation="10dp"
app:fabSize="normal"
app:rippleColor="#f00"
/>
其中:
1.src屬性是設置圖標
2.backgroundTint是設置背景色(圖標是透明背景的)
3.elevation是設置陰影大小
4.fabsize是設置圖標的大小,一般爲normal(不用設置)
5.rippleColor是設置水波紋的顏色
點擊事件如下(旋轉):
private boolean reverse = false;
public void rotate(View v) {
float toDegree = reverse ? -180f : 180f;
ObjectAnimator animator = ObjectAnimator
.ofFloat(v, "rotation", 0.0f, toDegree)
.setDuration(400);
animator.start();
reverse = !reverse;
}
FloatingActionButton動畫
方案1:列表滑動的時候FloatingActionButton隱藏與顯示,通過自定義OnScrollListener實現
public class FabScrollListener extends OnScrollListener {
private static final int THRESHOLD = 20;
private int distance = 0;
private HideScrollListener hideListener;
private boolean visible = true;
public FabScrollListener(HideScrollListener hideScrollListener) {
this.hideListener = hideScrollListener;
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
/**
* dy:Y軸方向的增量
* 有正和負
* 當正在執行動畫的時候,就不要再執行了
*/
if (distance > THRESHOLD && visible) {
visible = false;
hideListener.onHide();
distance = 0;
} else if (distance < -THRESHOLD && !visible) {
visible = true;
hideListener.onShow();
distance = 0;
}
if (visible && dy > 0 || (!visible && dy < 0)) {
distance += dy;
}
}
}
- 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
- 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
自定義一個OnScrollListener,重寫onScrolled方法。判斷當前的滾動方向、滾動距離、當前的FloatingActionButton是否顯示來進行相應的邏輯處理。
其中HideScrollListener是一個自定義的監聽接口:
public interface HideScrollListener {
void onHide();
void onShow();
}
由Activity實現這個接口:
public class FabAnimActivity extends AppCompatActivity implements HideScrollListener {
private RecyclerView recyclerview;
private ImageButton fab;
private Toolbar toolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
recyclerview.addOnScrollListener(new FabScrollListener(this));
}
@Override
public void onHide() {
toolbar.animate().translationY(-toolbar.getHeight()).setInterpolator(new AccelerateInterpolator(3));
RelativeLayout.LayoutParams layoutParams = (LayoutParams) fab.getLayoutParams();
fab.animate().translationY(fab.getHeight() + layoutParams.bottomMargin).setInterpolator(new AccelerateInterpolator(3));
}
@Override
public void onShow() {
toolbar.animate().translationY(0).setInterpolator(new DecelerateInterpolator(3));
fab.animate().translationY(0).setInterpolator(new DecelerateInterpolator(3));
}
}
- 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
- 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
方案2:自定義FloatingActionButton的Behavior實現
繼承FloatingActionButton的Behavior:
public class FabBehavior extends FloatingActionButton.Behavior {
private boolean visible = true;
public FabBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
if (dyConsumed > 0 && visible) {
visible = false;
onHide(child);
} else if (dyConsumed < 0) {
visible = true;
onShow(child);
}
}
public void onHide(FloatingActionButton fab) {
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
ViewCompat.animate(fab).scaleX(0f).scaleY(0f).start();
}
public void onShow(FloatingActionButton fab) {
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
ViewCompat.animate(fab).scaleX(1f).scaleY(1f).start();
}
}
- 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
- 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
構造方法必須重寫,重寫onStartNestedScroll返回判斷哪個方向的滑動,重寫onNestedScroll進行相應的邏輯處理(FloatingActionButton的屬性動畫顯示與隱藏)。
最後在佈局文件中使用CoordinatorLayout佈局,並且給FloatingActionButton添加自定義的Behavior:
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingTop="?attr/actionBarSize"
/>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:title="Fab動畫"
app:titleTextColor="#fff"/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="58dp"
android:layout_height="58dp"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:src="@drawable/ic_favorite_outline_white_24dp"
app:layout_behavior="com.nan.advancedui.fab.anim.behavior.FabBehavior"
/>
</android.support.design.widget.CoordinatorLayout>
- 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
- 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
CoordinatorLayout
CoordinatorLayout是一個繼承於ViewGroup的佈局容器。CoordinatorLayout監聽滑動子控件的滑動通過Behavior反饋到其他子控件並執行一些動畫。簡單來說,就是通過協調並調度裏面的子控件或者佈局來實現觸摸(一般是指滑動)產生一些相關的動畫效果。
其中,view的Behavior是通信的橋樑,我們可以通過設置view的Behavior來實現觸摸的動畫調度。
注意:滑動控件指的是:RecyclerView/NestedScrollView/ViewPager,意味着ListView、ScrollView不行。
詳細使用請參考 《CoordinatorLayout使用全解析》
MaterialDesign動畫
1.Touch Feedback(觸摸反饋)
5.0+的手機是自帶的。
通過給控件設置background的屬性值即可實現:
<Button
android:id="@+id/btn_test"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:background="?attr/selectableItemBackgroundBorderless"
android:text="測試"/>
其中,selectableItemBackground是有邊界的水波紋效果,selectableItemBackgroundBorderless是沒有邊界的水波紋效果。
可以修改背景顏色和水波紋的顏色,並且最好使用AppcompatActivity:
<item name="colorControlHighlight">@color/colorPrimary_pink</item>
<item name="colorButtonNormal">@color/material_blue_grey_800</item>
如果想改變個別控件的顏色的話,可以通過在外面再嵌套一層佈局實現。
2.Reveal Effect(揭露效果)
例子:Activity的揭露出現的效果。主要使用ViewAnimationUtil工具類實現:
ViewAnimationUtils.createCircularReveal(
view,
centerX, centerY,
startRadius,
endRadius)
其中,擴散的半徑通過勾股定理進行計算,例如:
(float) Math.hypot(view.getWidth() / 2, view.getHeight() / 2)
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus)
view_root = (LinearLayoutCompat) findViewById(R.id.llc_test)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Animator animator = ViewAnimationUtils.createCircularReveal(view_root, view_root.getWidth() / 2, view_root.getHeight() / 2, 0f, (float) Math.hypot(view_root.getWidth() / 2, view_root.getHeight() / 2))
animator.setDuration(1000)
animator.setInterpolator(new AccelerateInterpolator())
animator.start()
}
}
因爲動畫播放是依附在window上面的,而在Activity onCreate方法中調用時Window還未初始化完畢,因此需要在onWindowFocusChanged中執行動畫。
3.Activity transition(Activity轉場動畫效果)
兩個Activity進行跳轉的時候,轉場動畫。以前我們是通過overridePendingTransition方法實現。
主要使用ActivityOptions類。只支持API21以上的版本。版本判斷會比較麻煩,谷歌很貼心 設計了一個兼容類:ActivityOptionsCompat(v4包中),但是此類在低版本上面並沒有轉場動畫效果,只是解決了我們手動去判斷版本的問題而已。
使用轉換動畫前提:需要給兩個Activity都設置如下,讓其允許使用轉場動畫。
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
修改主題:<item name="android:windowContentTransitions">true</item>
轉場動畫可以分爲兩大類:共享元素轉換和普通的轉換。
1)共享元素轉換
單個元素:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(MDAnimActivity.this, iv_test, "test")
Intent intent = new Intent(MDAnimActivity.this, MDAnimSceneTransitionActivity.class)
startActivity(intent, options.toBundle())
}
多個元素同時轉換:
ActivityOptionsCompat optionsCompat = ActivityOptionsCompat
.makeSceneTransitionAnimation(this, Pair.create((View)iv1, "iv1"),Pair.create((View)bt, "bt"));
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent, optionsCompat.toBundle());
頁面返回的時候系統自動實現了,請看FragmentActivity的onBackPressed方法:
@Override
public void onBackPressed() {
if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
super.onBackPressed();
}
}
2.非共享元素的轉換
只有API 21纔有下面自帶效果,因此使用的時候需要判斷版本號。
三種系統帶的:滑動效果(Slide)、展開效果Explode、漸變顯示隱藏效果Fade。下面以Fade爲例子介紹:
Fade fade = new Fade();
fade.setDuration(1000);
getWindow().setExitTransition(fade);
getWindow().setEnterTransition(fade);
ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(MainActivity.this);
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent, optionsCompat.toBundle());