MeterailDesigner设计风格的demo,其中包括样式的使用实例,属性动画的使用,自定义VIew的使用,沉浸式状态栏,侧滑菜单,录像功能,design包中CoordinateLayout,RecycleView,Behavior等的使用例子 原本是想通过这个demo尽可能把所有Android技术点以代码和注释的方式包括进来,以备之后开发中到处去查资料的窘境,不过写着写着发现写不下去了,因为这是挑战一个大而全的技术知识点大全,不可能把所有细节淋漓尽致的体现出来,总是有顾此失彼的地方,后面想到用注释的方式把一些实现方式先注释起来,不过后面发现注释的代码很难阅读,并且显得整个代码很乱,所以后面打算通过每个小demo就只练习和挖掘一个或几个相似的技术的方法来继续后面的工作。不过之前所谓的大而全的代码还是舍不得删掉,虽然有点乱,不过毕竟是自己一个个代码堆积起来的,所以先上传上来,需要的时候再来查阅。下面我还是简单罗列一下这些技术点的使用位置吧
- SnackBar
- FloatingActionButton
- CoordinatorLayout
- TextInputLayout
- TabLayout
- AppBarLayout
- CollapsingToolbarLayout
- DrawerLayout
-
CollapsingToolbarLayout
的使用:CollapsingToolbarLayout ctlRoot = (CollapsingToolbarLayout) findViewById(R.id.ctl_root); //在滑动的过程中,会自动变化到该颜色。 ctlRoot.setContentScrimColor(usePaletteGetRgb()); //状态栏最终自动变化到该颜色 ctlRoot.setStatusBarScrimColor(usePaletteGetRgb());
-
item布局中使用
CardView
配合recyclerView使用 -
Palette
获取图片颜色值;
private int usePaletteGetRgb() {
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.mipmap.imv_scenery);
Palette.from(bmp).generate(new Palette.PaletteAsyncListener() {
@Override
public void onGenerated(Palette palette) {
Palette.Swatch swatch = palette.getVibrantSwatch();
if (swatch != null) {
color = swatch.getRgb();
}
}
});
return color;
}
ToolBar
实现沉浸式状态栏。
设置状态栏透明
<!-- 设置状态栏为透明--> <item name="android:windowTranslucentStatus">true</item> <!--Android 5.x开始需要把颜色设置透明,否则导航栏会呈现系统默认的浅灰色--> <item name="android:statusBarColor">@android:color/transparent</item>
布局中从toolbar开始到它的父布局全部设置设置fitSystemWindow 属性为true
自定义View
自定义View的实现方式大概可以分为三种,自绘控件、组合控件、以及继承控件 自绘控件(onMeasure-->onLayout-->onDraw)
-
BottomSheetBehavior
(design包里面)//返回这个View引用的BottomSheetBehavior,作为CoordinatorLayout子View(recyclerView)的LayoutParams //所以这里需要在recyclerView中设置 app:layout_behavior="@string/bottom_sheet_behavior" 属性 final BottomSheetBehavior behavior = BottomSheetBehavior.from(recyclerView); findViewById(R.id.btn_scroll).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (behavior.getState() == BottomSheetBehavior.STATE_EXPANDED) { behavior.setState(BottomSheetBehavior.STATE_COLLAPSED); } else if (behavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) { behavior.setState(BottomSheetBehavior.STATE_EXPANDED); } } }); /* bottomSheet 的状态监听与回调 */ behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { @Override public void onStateChanged(@NonNull View bottomSheet, int newState) { Log.e("onStateChanged", newState + ""); } @Override public void onSlide(@NonNull View bottomSheet, float slideOffset) { Log.e("onSlide", slideOffset + ""); } });
-
百分比布局
PercentRelativeLayout,PercentFrameLayoutLayout
可以在布局中设置百分比来控制控件在布局中的位置,类似于LinearLayout中的权重widget app:layout_heightPercent="20%" app:layout_widthPercent="30%"
-
真正的沉浸式模式
(一般只在游戏和视频类app里面用得到)/** * 在所有View绘制完毕的时候才会回调的 * * @param hasFocus 是否获得焦点 */ @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { View decorView = getWindow().getDecorView(); //获取当前界面的DecorView decorView.setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE //必须和下面UI FLag的配合同时使用 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION //让应用的主体内容占用系统导航栏的空间 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN //会让应用的主体内容占用系统状态栏的空间 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION //隐藏导航栏 | View.SYSTEM_UI_FLAG_FULLSCREEN //全屏 | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); } }
ConstraintLayout
约束布局的使用,主要是为了减少布局嵌套,直接拖拉控件实现
-
TextInputLayout
<android.support.design.widget.TextInputLayout android:id="@+id/til_user" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="请输入你的邮箱" tools:ignore="HardcodedText"> <android.support.v7.widget.AppCompatAutoCompleteTextView android:id="@+id/et_user" android:layout_width="match_parent" android:layout_height="wrap_content" android:completionHint="请选择用户名。。。" android:completionThreshold="1" android:inputType="textEmailAddress" /> </android.support.design.widget.TextInputLayout> //setError()方法如果传入非空参数,则setErrorEnabled(true)将自动被调用。 tilUser.setError("the input user style error,please check email..");
-
SharedPreferences
这里是用于保存所有登录成功的用户户名
private void saveHistory(String userName) {
SharedPreferences sp = getActivity().getSharedPreferences("history", Context.MODE_PRIVATE);
//取出之前保存的数据
Set<String> longHistory = sp.getStringSet("historyUser", new HashSet<String>());
if (!longHistory.contains(userName)) {
SharedPreferences.Editor editor = sp.edit();
users.add(userName);
users.addAll(longHistory);
editor.putStringSet("historyUser", users);
editor.apply();
}
}
- 调用系统相机进行拍照
intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
File folder = new File(SD_PATH);
if (!folder.exists()) {//判断是否已存在该文件夹
folder.mkdir();
}
File file = new File(SD_PATH + System.currentTimeMillis() + ".jpg");
//如果指定了目标uri,data就没有数据,如果没有指定uri,则data就返回有数据,照相机有自己默认的存储路径,
// 拍摄的照片将返回一个缩略图。如果想访问原始图片,可以通过dat extra能够得到原始图片位置.
picUri = Uri.fromFile(file);
intent.putExtra(MediaStore.EXTRA_OUTPUT, picUri);
startActivityForResult(intent, REQUEST_CODE_CAMERA);
- 调用系统相册
选择到的图片保存在onActivityResult()回调方法中的Intent data参数中,可以通过Uri picUri = data.getData();拿到结果 //picUri格式:content://media/external/images/media/7058
intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*"); //指定类型可以是image的任意类型,包括jpg/bmp/gif等等
startActivityForResult(intent, REQUEST_CODE_SELECT_PIC);
- 处理图片
最后就演变为对图片的uri进行处理,包括存储,剪裁,压缩,上传等处理方式,按照需求进行处理即可.
-
现将图片uri转换为bitmap,注意内存溢出
//方式一,可能会导致内存溢出 Bitmap srcBitmap = MediaStore.Images.Media.getBitmap(getContext().getContentResolver(), picUri); //方式二,使用输入流输出流处理,先压缩在读取成bitmap BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true;//只读边,不读内容 BitmapFactory.decodeStream(in, null, options); //返回值为空
-
压缩图片,分为按比例压缩(减少内存使用)和按质量压缩(减少图片大小)
//方式一,比例压缩 Bitmap bitmap = ThumbnailUtils.extractThumbnail(srcBitmap, imvPic.getWidth(), imvPic.getHeight(), ThumbnailUtils.OPTIONS_RECYCLE_INPUT); //方式二,质量压缩 /** * 图片压缩(质量压缩方法——适用于压缩之后上传到服务器) * <p/> * 它是在保持像素的前提下改变图片的位深及透明度等,来达到压缩图片的目的。 * 进过它压缩的图片文件大小会有改变,但是导入成bitmap后占得内存是不变的。 * 因为要保持像素不变,所以它就无法无限压缩,到达一个值之后就不会继续变小了。 */ public static Bitmap compressBitmap(Bitmap bitmap) throws IOException { ByteArrayOutputStream os = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os); //质量压缩方法,这里100表示不压缩,把压缩后的数据存放到os中 int options = 100; while (os.toByteArray().length / 1024 > 100) { //循环判断如果压缩后图片是否大于100kb,大于继续压缩 os.reset(); //重置os,即清空os //第一个参数:图片格式,第二个参数:图片质量,100为最高,0为最差,第三个参数:保存压缩后的数据的流 bitmap.compress(Bitmap.CompressFormat.JPEG, options, os); //这里压缩(100-options)%,把压缩后的数据存放到os中 if (options > 10) { options -= 10; } else { options = 80; } } byte[] bytes = os.toByteArray(); bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); return bitmap; }
NestedScrollView
支持嵌套滚动的ScrollViewRecyclerView
final Context mContext = getContext();
RecyclerView recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(mContext);
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
// StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(8, LinearLayoutManager.HORIZONTAL);
// GridLayoutManager gridLayoutManager = new GridLayoutManager(mContext, 3);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setNestedScrollingEnabled(false);//解决嵌套滚动的问题
recyclerView.setItemAnimator(new DefaultItemAnimator());//设置增加或删除条目的动画
recyclerView.addItemDecoration(new DividerItemDecoration(mContext, LinearLayoutManager.HORIZONTAL, 6, R.color.c_emphasize_blue));
// recyclerView.addItemDecoration(new DividerItemDecoration(mContext, LinearLayoutManager.VERTICAL, R.drawable.recycler_divider_style));
final RecyclerAdapter adapter = new RecyclerAdapter(mContext, data);
//如果item为固定高度时,使用这句可以提高效率
recyclerView.setHasFixedSize(true);
recyclerView.setAdapter(adapter);
adapter.setOnItemClickListener(new RecyclerAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
data.remove(position);
adapter.notifyItemRemoved(position);
//必须以snackBar弹出的父CoordinatorLayout作为相对的view
CoordinatorLayout parentCLRoot = (CoordinatorLayout) getActivity().findViewById(R.id.cl_root);
Snackbar.make(parentCLRoot, position + " 被删除了", Snackbar.LENGTH_LONG).show();
}
});
view滑动
的几种方式 方式一:调用View的layout方法来重新放置它的位置方式二:调用View的offset方法来重新放置它的位置,其实和方式一是一样的原理//获取View自身左边到其父布局左边的距离 --> v.getLeft() v.layout(v.getLeft() + offSetX, v.getTop() + offSetY, v.getRight() + offSetX, v.getBottom() + offSetY);
方式三:改变view所在的父布局的布局参数v.offsetLeftAndRight(offSetX); v.offsetTopAndBottom(offSetY);
方式四:移动view所在的父布局中所有的子viewRelativeLayout.LayoutParams layoutParams = ((RelativeLayout.LayoutParams) v.getLayoutParams()); layoutParams.leftMargin = v.getLeft() + offSetX; layoutParams.topMargin = v.getTop() + offSetY; v.setLayoutParams(layoutParams);
//将偏移量设置为负值 ((View) v.getParent()).scrollBy(-offSetX, -offSetY);
Android样式
的使用- selector
//虚线描边镂空圆角
<!--状态改变时,新状态展示时的淡入和淡出时间,以毫秒为单位-->
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:enterFadeDuration="5000"
android:exitFadeDuration="3000">
<!-- item是从上往下匹配的,如果匹配到一个item那它就将采用这个item -->
<item android:drawable="@drawable/bg_rectangle_with_stroke_dash" android:state_pressed="true" />
</selector>
- shape
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<!-- size设置形状的大小 -->
<size
android:width="@dimen/rectangle_corners"
android:height="@dimen/rectangle_corners" />
<!-- gradient设置渐变 -->
<!--使用radial渐变时,必须指定渐变的半径-->
<gradient
android:centerColor="@color/c_white"
android:endColor="@color/colorAccent"
android:gradientRadius="@dimen/rectangle_corners"
android:startColor="@color/c_emphasize_blue"
android:type="radial" />
</shape>
- rotate
<!--android:toDegrees 结束的角度度数,正数表示顺时针,负数表示逆时针-->
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:toDegrees="180"
android:pivotX="50%"
android:pivotY="50%"
android:visible="true">
<!--android:useLevel 一般为false,否则可能环形无法显示,
只有作为LevelListDrawable使用时才设为true-->
<shape
android:dither="true"
android:innerRadius="50dp"
android:innerRadiusRatio="3"
android:shape="ring"
android:thickness="10dp"
android:thicknessRatio="9"
android:useLevel="false">
<stroke
android:width="@dimen/stroke_width"
android:color="@android:color/holo_blue_bright" />
<!--sweep 扫描性渐变-->
<gradient
android:endColor="@android:color/holo_orange_dark"
android:startColor="@android:color/holo_orange_light"
android:type="sweep" />
</shape>
</rotate>
- layer-list
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<bitmap
android:mipMap="true"
android:gravity="center"
android:tileMode="clamp"
android:src="@mipmap/imv_ctl"/>
</item>
<item
android:left="10dp"
android:top="10dp">
<bitmap
android:mipMap="true"
android:gravity="center"
android:src="@mipmap/imv_scenery" />
</item>
<item
android:left="40dp"
android:top="40dp">
<bitmap
android:mipMap="true"
android:gravity="center"
android:src="@mipmap/imv_scenery"/>
</item>
</layer-list>
- 补间动画
首先在布局文件中定义动画
<!--shareInterpolator取值true或false,取true时,指在AnimationSet中定义一个插值器,它下面的所有动画共同。如果设为false,则表示它下面的动画自己定义各自的插值器。-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="6000"
android:shareInterpolator="true"
android:interpolator="@android:anim/anticipate_overshoot_interpolator">
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0" />
<scale
android:repeatCount="infinite"
android:repeatMode="reverse"
android:fromXScale="0.0"
android:fromYScale="0.0"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="1.0"
android:toYScale="1.0" />
<rotate
android:repeatCount="infinite"
android:repeatMode="reverse"
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="360" />
<translate
android:fromXDelta="-100"
android:fromYDelta="-100"
android:toXDelta="100"
android:toYDelta="100" />
</set>
然后在代码中进行设置
Animation animation = AnimationUtils.loadAnimation(getContext(), R.anim.set_anim_test);
btnAnimObject.startAnimation(animation);
- 属性动画
布局文件中定义动画样式
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:interpolator="@android:anim/decelerate_interpolator"
android:propertyName="rotation"
android:valueFrom="-90"
android:valueTo="180"
android:valueType="floatType" />
代码中进行调用
ObjectAnimator objectAnimator = (ObjectAnimator) AnimatorInflater.loadAnimator(getContext(), R.animator.object_animator_test);
objectAnimator.setTarget(btnAnimObject);
objectAnimator.start();
- 录制视频 初始化surfaceView和Camera
- 播放视频
- 有序列表
- 1
- 1.1
- 1.2
- 2
- 3
- 无序列表
- 1
- 2
- 3
- 4
- 5
这里是引用的*文字*
1+1>2
- 链接 github http://www.baidu.com
- 图片 头像
- 图片
斜体 粗体文字 加粗的斜体
标记符 | 含义 |
---|---|
# | 标题 |
* , - , + | 无序列表 |
1. | 有序列表 |
[] () | 链接 |
![] () | 图片 |
* * | 斜体 |
** ** | 粗体 |
~~ ~~ | 删除线 |
***,--- | 分割线 |
: | 表格中的对齐方式 |
` `,tab | 代码框 |
> | 大于符号 |
\ | 插入标记符 |
public static void main(){ System.out.println("Hello Markdown!"); }
public static void main(){
System.out.println("Hello Markdown!");
}
要删除的内容