项目地址:https://github.com/coolstar1204/MakePoster
这个界面控件出来也有好久了,可实际生产项目中,因为兼容老旧手机的需要,不能随便增加这些控件特性。只能是在学习项目中来折腾。在本项目中,主要是使用了com.android.support:appcompat-v7:23.2.0’、 ‘com.android.support:design:23.2.0’、 ‘com.android.support:cardview-v7:23.2.0’三个兼容库。
这个库就很简单了,只包括如下这十几个类:
使用起来也很简单:
"http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:cardCornerRadius="5dp" //卡片圆角半径
app:cardElevation="3dp" //卡片阴影高度
app:cardMaxElevation="6dp"
android:layout_margin="2dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
//...
app:cardElevation 阴影的高度
app:cardMaxElevation 阴影最大高度
app:cardBackgroundColor 卡片的背景色
app:cardCornerRadius 卡片的圆角大小
app:contentPadding 卡片内容于边距的间隔
app:cardUseCompatPadding 设置内边距,V21+的版本和之前的版本仍旧具有一样的计算方式
app:cardPreventConrerOverlap 在V20和之前的版本中添加内边距,这个属性为了防止内容和边角的重叠
另从http://blog.feng.moe/2015/10/24/something-about-cardview-development/ 处学习到:
在低版本中设置了 CardElevation 之后 CardView 会自动留出空间供阴影显示,而 Lollipop 之后则需要手动设置 Margin 边距来预留空间,导致我在设置 Margin 在 Android 5.x 机器上调试好后,在 Kitkat 机器调试时发现边距非常大,严重地浪费了屏幕控件。
因此,我们需要自定义一个 dimen 作为 CardView 的 Margin 值进行版本适配。
从图中可以看到,里面有本项目用到的CoordinatorLayout及相关辅助类、FloatingActionButton及相关辅助类、Snackbar及对应管理类都出自这个库。
CoordinatorLayout:本控件一般用于界面最底层容器,用于对上面的子View执行超过继承关系的事件管理或叫传递。有几个特定的控件可以使用Behavior方式对非子父控件关系的兄弟控件进行触摸、滚动、位置变化进行监听处理,达到非一条继承链条上的View进行联动变化的能力。
Snackbar:本控件我理解主要是代替Toast的,使用方式也差不多,不过界面更方便定制、同时可以有一个操作按钮自定义,更强大,本例中,就是改变了背景色来区分提示信息的类别。
FloatingActionButton:通过名称也可以知道,这个按钮最大的特色,就是浮动在界面其它控件上,哪怕那个控件不是它的父控件或兄弟控件,都可以通过layout_anchor、layout_anchorGravity属性让其浮动到其上面,同时通过layout_behavior指定行为方式,让其可随锚定控件的移动、缩放等进行关联变化或动画。
这个库主要是提供了新的ActionBar、Toolbar标题栏类组,AlertDialog对话框,ThemeUtils,AppCompatButton、AppCompatEditText、AppCompatCheckedTextView一套以AppCompat开关的可视控件组,本例中主要用到了AppCompatCheckedTextView。EditText。
这个的作用是定义了一个叫AppTheme.Base的基本主题,其在本例中是从Theme.AppCompat.Light.NoActionBar继承而来,这个parent一定要指定你最小的应用支持的版本支持的主题。本例中的minSdkVersion 15,所以NoActionBar就可以正常运行在4.0.3及以后版本手机上。上面中还需要注意的最后一个item,是指定RecyclerView中的分割条drawable用的。在自定义分割条类中直接读取的。
同时在values目录和values-v21目录中,都从上面的AppTheme.Base继承定义了AppTheme自定义主题,values中的无扩展设置,但values-v21中的,针对Material Design增加了一些设置属性,有需要的地方,就需要自己设置修改了:
其中各item意义如下:
– android:windowContentTransitions:开启转场动画效果(炫酷转换就要设置为true)
– android:windowSharedElementEnterTransition、android:windowSharedElementExitTransition:指定共享元素的转场动画效果,这里使用了系统定义的move动画。
– android:windowAllowEnterTransitionOverlap、android:windowAllowReturnTransitionOverlap:这二个是指示是否过渡动画在进入和退出的二个窗体中重叠执行。(貌似我使用的move动画不受这二个参数影响,true和false都差不多)
在二个Activity的界面布局中,指定二个View使用相同的android:transitionName属性,在本程序中,MainActivity界面动画打开PosterActivity界面,所以我在MainActivity的界面中,把android:transitionName指定到主界面RecyclerView中的ViewHolder用的界面布局xml中显示小图的ImageView控件中,也就是主界面有多图时,会有多图都有这个属性设置,而在PosterActivity中,我把这个transitionName属性指定给了最外罢的容器CoordinatorLayout控件,但显示图的控件是其子控件,自定义的TextDrawer,同样达到了在MainAcitvity中的小图进行放大动画转场到PosterActivity的大图效果。证明不需要有相同android:transitionName设置的控件必须是都是加载相同图片的控件,而且我在开发中,还发现我如果把android:transitionName属性指定给TextDrawer后,可能 是因为自定义绘制原因,在动画过程中,多次触发onSizeChanged事件,但界面却不重绘,最终转场后,图片还是原来主界面大小显示状态,要强制刷新一下才放大显示的问题,换成标准控件ImageView就没问题。后来是把android:transitionName属性移到CoordinatorLayout后,才能正常缩放动画转场。
使用ActivityOptionsCompat,在进行Activity的跳转时,使用如下代码,才能支持转场动画 :
private void openPosterActivity(PictureInfo item,View v) {
ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(this,v,getString(R.string.main_trans_imgname));
Bundle bundle = optionsCompat.toBundle();
Intent intent = new Intent(this, PosterActivity.class);
intent.putExtra("PictureInfo",item);
ActivityCompat.startActivity(this,intent,bundle);
}
其中main_trans_imgname就是指定的android:transitionName,v就是当前点击的RecyclerView的项布局中指定了transitonName的ImageView控件。我理解这里的v一定要传有transitonName指定的控件。
—————————————–至此、转换动画几处设置结束————————————-
listView = (RecyclerView)findViewById(R.id.main_recylerView);
//listView.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false));
//listView.setLayoutManager(new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL));
GridLayoutManager gridLayoutMgr = new GridLayoutManager(this,2);
gridLayoutMgr.setOrientation(GridLayoutManager.VERTICAL);
listView.setLayoutManager(new GridLayoutManager(this,2));
DividerGridItemDecoration decoration = new DividerGridItemDecoration(this);
listView.addItemDecoration(decoration);
listView.setItemAnimator(new DefaultItemAnimator());
public class DependentBehavior extends CoordinatorLayout.Behavior<View> {
private float initChildTop;
public DependentBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof RelativeLayout; //依赖相对布局的类
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
if(dependency instanceof RelativeLayout){
if(initChildTop==0){
initChildTop = child.getTop();
}
float totalTransY = ScreenUtility.dip2px(160);
float rotateRank = (child.getTop()-initChildTop)/totalTransY*180;
child.setRotation(rotateRank);
ViewCompat.setTranslationY(child,-child.getHeight()/3); //这里的除3是根据图标大小计算,但为什么只设置一个固定值就能改变transY,还不知道原因?
return true;
}
return super.onDependentViewChanged(parent, child, dependency);
}
//...去掉空实现的函数
}
public void showMessage(String msg) {
Snackbar snackbar = Snackbar.make(mDrawer,msg,Snackbar.LENGTH_SHORT);
ColoredSnackbar.info(snackbar).show();
}
public class ColoredSnackbar {
private static final int red = 0xfff44336;
private static final int green = 0xff4caf50;
private static final int blue = 0xff2195f3;
private static final int orange = 0xffffc107;
private static View getSnackBarLayout(Snackbar snackbar) {
if (snackbar != null) {
return snackbar.getView();
}
return null;
}
private static Snackbar colorSnackBar(Snackbar snackbar, int colorId) {
View snackBarView = getSnackBarLayout(snackbar);
if (snackBarView != null) {
snackBarView.setBackgroundColor(colorId);
}
return snackbar;
}
public static Snackbar info(Snackbar snackbar) {
return colorSnackBar(snackbar, blue);
}
public static Snackbar warning(Snackbar snackbar) {
return colorSnackBar(snackbar, orange);
}
public static Snackbar alert(Snackbar snackbar) {
return colorSnackBar(snackbar, red);
}
public static Snackbar confirm(Snackbar snackbar) {
return colorSnackBar(snackbar, green);
}
}