MaterialDesign

Material Design的风格学习

android 5.0 使用Material Design风格主题


分别是:

@android:style/Theme.Material  (dark version)  

@android:style/Theme.Material.Light  (light version)  

@android:style/Theme.Material.Light.DarkActionBar

light

dark

我们可以使用这三个theme来定义我们自己的theme

<resources>
 <style name="AppTheme" parent="android:Theme.Material">
   <!-- your app branding color for the app bar -->
   <item name="android:colorPrimary">@color/primary</item>
   <item name="android:colorPrimaryDark">@color/primary_dark</item>
   <item name="android:colorAccent">@color/accent</item>
 </style>
</resources>

修改每个位置的颜色,每个位置的名字:

在低版本使用Material Design 风格


加入supprot library

dependencies {
    compile 'com.android.support:appcompat-v7:+'
    compile 'com.android.support:cardview-v7:+'
    compile 'com.android.support:recyclerview-v7:+'
}

将上面的appTheme style 放到res/values-v21/style.xml, 再res/values/style.xml增加一个AppTheme.

<style name="Theme.MyTheme" parent="Theme.AppCompat.Light">
  <!-- customize the color palette -->
  <item name="colorPrimary">@color/material_blue_500</item>
  <item name="colorPrimaryDark">@color/material_blue_700</item>
  <item name="colorAccent">@color/material_green_A200</item>
</style>

这是主题的沉浸式状态栏。

创建列表和卡片


卡片布局是Material Design 的另外一个组成部分
在引用时需要引入依赖包

dependencies {
    compile 'com.android.support:appcompat-v7:+'
    compile 'com.android.support:cardview-v7:+'
    compile 'com.android.support:recyclerview-v7:+'
}
创建list

recyclerView组件是一个更加灵活的listview, 这个组件时一个显示大数据集的容器,

可以有效的滚动,保持显示一定数量的视图.
使用RecyclerView组件,当你有数据集,并且数据集的元素在运
行时根据用户的操作或者网络事件改变。
RecylerView类简化大数据集的显示和处理,通过提供布局管理者控制元素定位.

在通用的元素上操作上显示默认的动画,比如移除和增加元素。
使用RecyclerView组件,你需要指定一个Adapter和布局管理器,
创建一个Adapter继承RecyclerView.Adapter类,具体的实现细节要根据数据集合视图的类型变化,
具体信息,看下面的例子。
一个布局管理器定位Item视图在RecyclerView中,决定什么时候去回收它当他不再可见时。当重用(或者回收)一个视图时,布局管理器可能会请求适配器(Adapter)去替换子视图中的内容用不同的内容。通过这种方式回收重用视图,可以减少view的创建和避免更多的findViewById(),从而提高性能。
recyclerview 提供了以下内建的布局管理器:

  • LinearLayoutManager 显示Item 在一个水平或者垂直的滚动列表中。
  • GridLayoutManager 显示Item 作为网格布局。
  • StaggeredGridLayoutManager 显示Item在交错的网格布局

也可以自己通过继承RecyclerView.LayoutManager类来创建一个自定义的布局管理器。

动画

RecyclerView默认情况下就有动画,在删除或者增加Ite的时候.
如果需要自定义动画,继承RecyclerView.ItemAnimator, 并且使用RecyclerView.setItemAnimator()
来将动画设置到我们的视图中。
下面来看例子:

  1. 在xml布局增加一个RecyclerView.

    <android.supporv7.widget.RecycleView
        android:id="@+id/my_recycler_view"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>  
    
  2. 然后在java代码中使用,附加adapter和数据就行了。

    public class MyActivity extends Activity {
        private RecyclerView mRecyclerView;
        private RecyclerView.Adapter mAdapter;
        private RecyclerView.LayoutManager mLayoutManager;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.my_activity);
            mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
            // use this setting to improve performance if you know that changes
            // in content do not change the layout size of the RecyclerView
            mRecyclerView.setHasFixedSize(true);
            // use a linear layout manager
            mLayoutManager = new LinearLayoutManager(this);
            mRecyclerView.setLayoutManager(mLayoutManager);
            // specify an adapter (see also next example)
            mAdapter = new MyAdapter(myDataset);
            mRecyclerView.setAdapter(mAdapter);
        }
        ...
    }  
    
  3. adapter提供访问数据集中item,创建试图映射到数据上,并且替换布局的内容数据用新的item:

    public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
        private String[] mDataset;
        // Provide a reference to the views for each data item
        // Complex data items may need more than one view per item, and
        // you provide access to all the views for a data item in a view holder
        public static class ViewHolder extends RecyclerView.ViewHolder {
            // each data item is just a string in this case
            public TextView mTextView;
            public ViewHolder(TextView v) {
                super(v);
                mTextView = v;
            }
        }
        // Provide a suitable constructor (depends on the kind of dataset)
        public MyAdapter(String[] myDataset) {
            mDataset = myDataset;
        }   
        // Create new views (invoked by the layout manager)
        @Override
        public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                      int viewType) {
            // create a new view
            View v = LayoutInflater.from(parent.getContext())
                         .inflate(R.layout.my_text_view, parent, false);
            // set the view's size, margins, paddings and layout parameters
            ...
            ViewHolder vh = new ViewHolder(v);
            return vh;
        }
        // Replace the contents of a view (invoked by the layout manager)
        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            // - get element from your dataset at this position
            // - replace the contents of the view with that element
            holder.mTextView.setText(mDataset[position]);
        }
        // Return the size of your dataset (invoked by the layout manager)
        @Override
        public int getItemCount() {
            return mDataset.length;
        }
    }
    
创建Card

CardView继承FrameLayout类,通过它可以显示信息在卡片内部,并且在不同的平台上有统一的样式
CardVIew组件可以有阴影和圆角。
创建有阴影的Card,使用card_view:cardElevati属性。
设置圆角的半径:在布局文件里,card_view:cardCornerRadius;在java里,使用CardView.setRadius
而背景颜色使用card_view:cardBackgroundColor。

在布局文件里包含CardView:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    ... >
    <!-- A CardView that contains a TextView -->
    <android.support.v7.widget.CardView
        xmlns:card_view="http://schemas.android.com/apk/res-auto"
        android:id="@+id/card_view"
        android:layout_gravity="center"
        android:layout_width="200dp"
        android:layout_height="200dp"
        card_view:cardCornerRadius="4dp">
        <TextView
            android:id="@+id/info_text"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </android.support.v7.widget.CardView>
</LinearLayout>

图片实例:

通常是把RecyclerView , cardView 写在一起。

定义阴影和剪裁视图


无法向下兼容,只可以在5.0及以上实现。
视图的高度(elevation),通过Z属性表现.

为视图分配高度

一个View的Z值有两部分组成,elevation(高度)和translation(平移)
elevation是一个静态部分, translation用于动画。
不同高度的视图的阴影

在布局文件设置elevation: android:elevation 在代码里使用:View.setElevation()
设置一个视图的平移,使用View.setTranslationZ().

新的方法ViewPropertyAnimator.z()和ViewPropertyAnimator.translationZ() 更容易实现变动视图的平移。

自定义视图阴影和轮廓

视图的背景边界决定了阴影的默认形状,轮廓(outlines)代表了图形的对象的外形状,
并确定了对触摸反馈的波纹。
这个视图,定义一个背景Drawable:

背景是一个圆角矩形myrect.xml:
!— res/drawable/myrect.xml —>




当这个背景drawable做为视图的轮廓时,视图投射出圆角阴影。
提供一个自定义的轮廓,可以覆盖默认视图阴影的形状。
在自己的代码里自定义一个轮廓:

  1. 继承ViewOutlineProvider
  2. 重写getOutline()方法
  3. 在视图里面设置轮廓,使用View.setOutlineProvider()方法。

创建椭圆和圆角矩形轮廓使用Outline中的方法。
视图默认的outline provider 会根据视图的背景来生成轮廓。
可以设置视图的outline prioder为 null ,来阻止投射阴影。

剪裁视图

裁剪视图,更容易改变视图的形状。
常用的方法:

  1. View.setClipToOutLine()方法裁剪一个视图的轮廓。(android:clipToOutline)
  2. Outline.canClip()方法检测是否支持被裁剪。
  3. View.setClipToOutline().
总结

设置阴影很简单:

  1. 设置elevation值
  2. 添加背景或是设置一个outline .

RecyclerView详解

RecyclerVIew的特点:你可以通过设置LayoutManger来快速实现listView、gridView、瀑布流的效果
而且还可以横向纵向显示,添加动画也比较方面,自带itemAnimation,可以设置加载和移除时的动画

item的动画实现

只需要调用以下几个函数来增删item即可出现默认动画。
notifyItemChanged(int)
notifyItemInserted(int)
notifyIteemRemoved(int)
notifyItemRangeChanged(int, int)
notifyItemRangeInserted(int, int)
notifyItemRangeRemoved(int, int)

也可以看开源库: [recyclerview-animators]{https://github.com/wasabeef/recyclerview-animators}

首先是布局文件里加入 RecyclerView
然后在activity中

RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
//创建一个线性布局管理器
LinearLayoutManger layoutManger = new LinearLayoutManger(this);
//设置布局管理器,线性显示
recyclerView.setLayoutManger(layoutManger);
//recyclerView.setLayoutManger(new GridLayoutManger(this,2));//这里线性宫格显示,
//recyclerView.setLayoutManger(new StaggeredGridLayoutManger(2, orientationHelper.VERTICAL));//类似瀑布流
//创建数据集
String[] dataset = new String[100];
for (int i=0; i<dataset.length; i++) {
    dataset[i] = "item" + i;
}
//创建Adapter ,并指定数据集
MyAdapter adapter = new MyAdapter(dataset);
//设置adapter
recyclerView.setAdapter(adapter);

recyclerView首先的一个特点就是将layout抽象成一个LayoutManger, recyclerView不负责
子view的布局,自定义LayoutManger来实现不同的布局效果,目前只有LinearLayoutManger。
adapter是怎么实现

public class MyAdapter extends REcyclerView.Adapter<MyAdapter.ViewHolder> {
    //数据集
    private String[] mDataset;
    public MyAdapter(String[] dataset) {
        super();
        mDataset = dataset;
    }

    //必须重写的三个方法
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        //在这里设item布局
        return holder;
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int i) {
        //绑定数据到ViewHolder上

    }

    @Override
    public int getItemCount() {
        return mDataset.length;
    }

    public static class ViewHolder extends RecyclerView.ViewHolder{

        public ViewHolder (View view) {
            super(view);
            //
        }
    }

}

recyclerView的另外一个特点时标准化了ViewHolder,编写adapter面向的是ViewHolder而不在view,
复用的逻辑被封装了,更简单。

实现不同的效果,重要在于adapter,写一个复杂点的adapter

public class MultipleItemAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    public static enum ITEM_TYPE {
        ITEM_TYPE_IMAGE,
        ITEM_TYPE_TEXT
    }
    private final LayoutInflater mLayoutInflater;
    private final Context mContext;
    private String[] mTitles;

    public MultipleItemAdapter (Context context) {
        mTitles = context.getResources().getStringArray(R.array.titles);
        mCOntext = context;
        mLayoutInflater = LayoutInflater.from(context);
    }
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
        if (viewType == ITEM_TYPE.ITEM_TYPE_IMAGE.ordinal()){
            return new ImageViewHolder(mLayoutInflater.inflater(R.layout.item_image,parent,false));
        } else {
            return new TextViewHolder(mLayoutInflater.inflater(R.layout.item_text,parent,false));
        }
    }

    @Override 
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof TextViewHolder) {
            ((TextViewHolder) holder).mTextView.setText(mTiles[position]);
        } else if(holder instanceof ImageViewHolder) {
            ((ImageViewHolder) holder).mTextView.setText(mTiles[position]);
        }
    }

    @Override
    public int getItemViewType(int position) {
        return position % 2 == 0 ? ITEM_TYPE.ITEM_TYPE_IMAGE.ordinal() : ITEM_TYPE
                                    .ITEM_TYPE_TEXT.ordinal();
    }

    public static class TextViewHolder extends RecyclerView.ViewHolder {
        @InjectView(R.id.text_view)
        TextView mTextView;
        TextViewHolder(View v) {
            super(view);
            ButterKnife.inject(this, view);
            view.setOnclickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.d("TextViewHolder", "onClick--> position = " + getPosition());
                }
            });
        }
    }
    public static class ImageViewHolder extends RecyclerView.ViewHolder {
        @InjectView(R.id.text_view)
        TextView mTextView;
        @InjectView(R.id.image_view)
        ImageView mImageView;
        TextView mTextView;
        ImageViewHolder(View v) {
            super(view);
            ButterKnife.inject(this, view);
            view.setOnclickListener(new View.OnClickListener() {
                @Overrid
                public void onClick(View v) {
                    Log.d("TextViewHolder", "onClick--> position = " + getPosition());
                }
            });
        }
    }
}

waiting

chenzhao@hustiunique.com