7.侧滑、ViewDragHelper、属性动画
* 应用场景: 扩展主面板的功能
* 功能实现:
> 1. ViewDragHelper: Google2013年IO大会提出的,
> 解决界面控件拖拽移动问题. (v4包下)
> 2. mTouchSlop 最小敏感范围, 值越小, 越敏感
* 伴随动画:
> 1. 左面板: 缩放动画, 平移动画, 透明度动画
> 2. 主面板: 缩放动画
> 3. 背景动画: 亮度变化 (颜色变化)
* 状态监听\触摸优化:
> 1. 设置并更新状态
> 2. 触摸优化: 重写ViewGroup里onInterceptTouchEvent和onTouchEvent
新v4、看大小
nineoldandroids.jar 属性动画,兼容9个低版本
ActionBarSherlock
<com.drag.DragLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/dl"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg"
tools:context=".MainActivity" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="50dp"
android:paddingLeft="10dp"
android:paddingRight="50dp"
android:paddingTop="50dp" >
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/head" />
<ListView
android:id="@+id/lv_left"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ListView>
</LinearLayout>
<com.drag.MyLinearLayout
android:id="@+id/mll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="vertical" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#18B6EF"
android:gravity="center_vertical" >
<ImageView
android:id="@+id/iv_header"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="15dp"
android:src="@drawable/head" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="Header" />
</RelativeLayout>
<ListView
android:id="@+id/lv_main"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ListView>
</com.drag.MyLinearLayout>
</com.drag.DragLayout>Utils:单例的toast
public class Utils {
public static Toast mToast;
public static void showToast(Context mContext, String msg) {
if (mToast == null) {
mToast = Toast.makeText(mContext, "", Toast.LENGTH_SHORT);
}
mToast.setText(msg);
mToast.show();
} /**
* dip 转换成 px
* @param dip
* @param context
* @return
*/
public static float dip2Dimension(float dip, Context context) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, displayMetrics);
}
/**
* @param dip
* @param context
* @param complexUnit {@link TypedValue#COMPLEX_UNIT_DIP} {@link TypedValue#COMPLEX_UNIT_SP}}
* @return
*/
public static float toDimension(float dip, Context context, int complexUnit) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
return TypedValue.applyDimension(complexUnit, dip, displayMetrics);
}
/** 获取状态栏高度
* @param v
* @return
*/
public static int getStatusBarHeight(View v) {
if (v == null) {
return 0;
}
Rect frame = new Rect();
v.getWindowVisibleDisplayFrame(frame);
return frame.top;
}
public static String getActionName(MotionEvent event) {
String action = "unknow";
switch (MotionEventCompat.getActionMasked(event)) {
case MotionEvent.ACTION_DOWN:
action = "ACTION_DOWN";
break;
case MotionEvent.ACTION_MOVE:
action = "ACTION_MOVE";
break;
case MotionEvent.ACTION_UP:
action = "ACTION_UP";
break;
case MotionEvent.ACTION_CANCEL:
action = "ACTION_CANCEL";
break;
case MotionEvent.ACTION_SCROLL:
action = "ACTION_SCROLL";
break;
case MotionEvent.ACTION_OUTSIDE:
action = "ACTION_SCROLL";
break;
default:
break;
}
return action;
}
}DragLayout:
/**
* 侧滑面板
* @author poplar
*
*/
public class DragLayout extends FrameLayout {
private static final String TAG = "TAG";
private ViewDragHelper mDragHelper;
private ViewGroup mLeftContent;
private ViewGroup mMainContent;
private OnDragStatusChangeListener mListener;
private Status mStatus = Status.Close; /**
* 状态枚举
*/
public static enum Status {
Close, Open, Draging;
}
public interface OnDragStatusChangeListener{
void onClose();
void onOpen();
void onDraging(float percent);
} public Status getStatus() {
return mStatus;
}
public void setStatus(Status mStatus) {
this.mStatus = mStatus;
}
public void setDragStatusListener(OnDragStatusChangeListener mListener){
this.mListener = mListener;
} public DragLayout(Context context) {
this(context, null);
}
public DragLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DragLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle); // a.初始化 (通过静态方法)
mDragHelper = ViewDragHelper.create(this , mCallback); } ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
// c. 重写事件 // 1. 根据返回结果决定当前child是否可以拖拽
// child 当前被拖拽的View
// pointerId 区分多点触摸的id
@Override
public boolean tryCaptureView(View child, int pointerId) {
Log.d(TAG, "tryCaptureView: " + child);
return true;
}; @Override
public void onViewCaptured(View capturedChild, int activePointerId) {
Log.d(TAG, "onViewCaptured: " + capturedChild);
// 当capturedChild被捕获时,调用.
super.onViewCaptured(capturedChild, activePointerId);
}
@Override
public int getViewHorizontalDragRange(View child) {
// 返回拖拽的范围, 不对拖拽进行真正的限制. 仅仅决定了动画执行速度
return mRange;
} // 2. 根据建议值 修正将要移动到的(横向)位置 (重要)
// 此时没有发生真正的移动
public int clampViewPositionHorizontal(View child, int left, int dx) {
// child: 当前拖拽的View
// left 新的位置的建议值, dx 位置变化量
// left = oldLeft + dx;
Log.d(TAG, "clampViewPositionHorizontal: "
+ "oldLeft: " + child.getLeft() + " dx: " + dx + " left: " +left); if(child == mMainContent){
left = fixLeft(left);
}
return left;
}
// 3. 当View位置改变的时候, 处理要做的事情 (更新状态, 伴随动画, 重绘界面)
// 此时,View已经发生了位置的改变
@Override
public void onViewPositionChanged(View changedView, int left, int top,
int dx, int dy) {
// changedView 改变位置的View
// left 新的左边值
// dx 水平方向变化量
super.onViewPositionChanged(changedView, left, top, dx, dy);
Log.d(TAG, "onViewPositionChanged: " + "left: " + left + " dx: " + dx); int newLeft = left;
if(changedView == mLeftContent){
// 把当前变化量传递给mMainContent
newLeft = mMainContent.getLeft() + dx;
} // 进行修正
newLeft = fixLeft(newLeft); if(changedView == mLeftContent) {
// 当左面板移动之后, 再强制放回去.
mLeftContent.layout(0, 0, 0 + mWidth, 0 + mHeight);
mMainContent.layout(newLeft, 0, newLeft + mWidth, 0 + mHeight);
}
// 更新状态,执行动画
dispatchDragEvent(newLeft); // 为了兼容低版本, 每次修改值之后, 进行重绘
invalidate();
}
// 4. 当View被释放的时候, 处理的事情(执行动画)
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
// View releasedChild 被释放的子View
// float xvel 水平方向的速度, 向右为+
// float yvel 竖直方向的速度, 向下为+
Log.d(TAG, "onViewReleased: " + "xvel: " + xvel + " yvel: " + yvel);
super.onViewReleased(releasedChild, xvel, yvel); // 判断执行 关闭/开启
// 先考虑所有开启的情况,剩下的就都是关闭的情况
if(xvel == 0 && mMainContent.getLeft() > mRange / 2.0f){
open();
}else if (xvel > 0) {
open();
}else {
close();
} }
@Override
public void onViewDragStateChanged(int state) {
// TODO Auto-generated method stub
super.onViewDragStateChanged(state);
}
}; /**
* 根据范围修正左边值
* @param left
* @return
*/
private int fixLeft(int left) {
if(left < 0){
return 0;
}else if (left > mRange) {
return mRange;
}
return left;
} protected void dispatchDragEvent(int newLeft) {
float percent = newLeft * 1.0f/ mRange;
//0.0f -> 1.0f
Log.d(TAG, "percent: " + percent); if(mListener != null){
mListener.onDraging(percent);
} // 更新状态, 执行回调
Status preStatus = mStatus;
mStatus = updateStatus(percent);
if(mStatus != preStatus){
// 状态发生变化
if(mStatus == Status.Close){
// 当前变为关闭状态
if(mListener != null){
mListener.onClose();
}
}else if (mStatus == Status.Open) {
if(mListener != null){
mListener.onOpen();
}
}
} // * 伴随动画:
animViews(percent); }
private Status updateStatus(float percent) {
if(percent == 0f){
return Status.Close;
}else if (percent == 1.0f) {
return Status.Open;
}
return Status.Draging;
}
private void animViews(float percent) {
// > 1. 左面板: 缩放动画, 平移动画, 透明度动画
// 缩放动画 0.0 -> 1.0 >>> 0.5f -> 1.0f >>> 0.5f * percent + 0.5f
// mLeftContent.setScaleX(0.5f + 0.5f * percent);
// mLeftContent.setScaleY(0.5f + 0.5f * percent);
ViewHelper.setScaleX(mLeftContent, evaluate(percent, 0.5f, 1.0f));
ViewHelper.setScaleY(mLeftContent, 0.5f + 0.5f * percent);
// 平移动画: -mWidth / 2.0f -> 0.0f
ViewHelper.setTranslationX(mLeftContent, evaluate(percent, -mWidth / 2.0f, 0));
// 透明度: 0.5 -> 1.0f
ViewHelper.setAlpha(mLeftContent, evaluate(percent, 0.5f, 1.0f)); // > 2. 主面板: 缩放动画
// 1.0f -> 0.8f
ViewHelper.setScaleX(mMainContent, evaluate(percent, 1.0f, 0.8f));
ViewHelper.setScaleY(mMainContent, evaluate(percent, 1.0f, 0.8f)); // > 3. 背景动画: 亮度变化 (颜色变化)
getBackground().setColorFilter((Integer)evaluateColor(percent, Color.BLACK, Color.TRANSPARENT), Mode.SRC_OVER);
} /**
* 估值器,0-100,一半50百分之=50,10-100
* @param fraction
* @param startValue
* @param endValue
* @return
*/
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
/**
* 颜色变化过度
* @param fraction
* @param startValue
* @param endValue
* @return
*/
public Object evaluateColor(float fraction, Object startValue, Object endValue) {
int startInt = (Integer) startValue;
int startA = (startInt >> 24) & 0xff;
int startR = (startInt >> 16) & 0xff;
int startG = (startInt >> 8) & 0xff;
int startB = startInt & 0xff;
int endInt = (Integer) endValue;
int endA = (endInt >> 24) & 0xff;
int endR = (endInt >> 16) & 0xff;
int endG = (endInt >> 8) & 0xff;
int endB = endInt & 0xff;
return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
(int)((startR + (int)(fraction * (endR - startR))) << 16) |
(int)((startG + (int)(fraction * (endG - startG))) << 8) |
(int)((startB + (int)(fraction * (endB - startB))));
}
@Override
public void computeScroll() {
super.computeScroll(); // 2. 持续平滑动画 (高频率调用)
if(mDragHelper.continueSettling(true)){
// 如果返回true, 动画还需要继续执行
ViewCompat.postInvalidateOnAnimation(this);
}
} public void close(){
close(true);
}
/**
* 关闭
*/
public void close(boolean isSmooth) {
int finalLeft = 0;
if(isSmooth){
// 1. 触发一个平滑动画
if(mDragHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)){
// 返回true代表还没有移动到指定位置, 需要刷新界面.
// 参数传this(child所在的ViewGroup)
ViewCompat.postInvalidateOnAnimation(this);
}
}else {
mMainContent.layout(finalLeft, 0, finalLeft + mWidth, 0 + mHeight);
}
} public void open(){
open(true);
}
/**
* 开启
*/
public void open(boolean isSmooth) {
int finalLeft = mRange;
if(isSmooth){
// 1. 触发一个平滑动画
if(mDragHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)){
// 返回true代表还没有移动到指定位置, 需要刷新界面.
// 参数传this(child所在的ViewGroup)
ViewCompat.postInvalidateOnAnimation(this);
}
}else {
mMainContent.layout(finalLeft, 0, finalLeft + mWidth, 0 + mHeight);
}
}
private int mHeight;
private int mWidth;
private int mRange; // b.传递触摸事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 传递给mDragHelper
return mDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
try {
mDragHelper.processTouchEvent(event);
} catch (Exception e) {
e.printStackTrace();
}
// 返回true, 持续接受事件
return true;
} @Override
protected void onFinishInflate() {
super.onFinishInflate();
// Github
// 写注释
// 容错性检查 (至少有俩子View, 子View必须是ViewGroup的子类) if(getChildCount() < 2){
throw new IllegalStateException("布局至少有俩孩子. Your ViewGroup must have 2 children at least.");
}
if(!(getChildAt(0) instanceof ViewGroup && getChildAt(1) instanceof ViewGroup)){
throw new IllegalArgumentException("子View必须是ViewGroup的子类. Your children must be an instance of ViewGroup");
} mLeftContent = (ViewGroup) getChildAt(0);
mMainContent = (ViewGroup) getChildAt(1);
} @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 当尺寸有变化的时候调用 mHeight = getMeasuredHeight();
mWidth = getMeasuredWidth(); // 移动的范围
mRange = (int) (mWidth * 0.6f); } }MyLinearLayout:主页面,在打开侧边栏或者拖拽时不让主页面里的listview滑动
public class MyLinearLayout extends LinearLayout {
private DragLayout mDragLayout;
public MyLinearLayout(Context context) {
super(context);
}
public MyLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
} public void setDraglayout(DragLayout mDragLayout){
this.mDragLayout = mDragLayout;
} @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 如果当前是关闭状态, 按之前方法判断
if(mDragLayout.getStatus() == Status.Close){
return super.onInterceptTouchEvent(ev);
}else {
return true;
}
} @Override
public boolean onTouchEvent(MotionEvent event) {
// 如果当前是关闭状态, 按之前方法处理
if(mDragLayout.getStatus() == Status.Close){
return super.onTouchEvent(event);
}else {
// 手指抬起, 执行关闭操作
if(event.getAction() == MotionEvent.ACTION_UP){
mDragLayout.close();
} return true;
}
}
}MainActivity
public class MainActivity extends Activity {
private static final String TAG = "TAG";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main); final ListView mLeftList = (ListView) findViewById(R.id.lv_left);
final ListView mMainList = (ListView) findViewById(R.id.lv_main);
final ImageView mHeaderImage = (ImageView) findViewById(R.id.iv_header);
MyLinearLayout mLinearLayout = (MyLinearLayout) findViewById(R.id.mll); // 查找Draglayout, 设置监听
DragLayout mDragLayout = (DragLayout) findViewById(R.id.dl);
// 设置引用
mLinearLayout.setDraglayout(mDragLayout); mDragLayout.setDragStatusListener(new OnDragStatusChangeListener() { @Override
public void onOpen() {
Utils.showToast(MainActivity.this, "onOpen");
// 左面板ListView随机设置一个条目
Random random = new Random(); int nextInt = random.nextInt(50);
mLeftList.smoothScrollToPosition(nextInt); } @Override
public void onDraging(float percent) {
Log.d(TAG, "onDraging: " + percent);// 0 -> 1
// 更新图标的透明度
// 1.0 -> 0.0
ViewHelper.setAlpha(mHeaderImage, 1 - percent);
} @Override
public void onClose() {
Utils.showToast(MainActivity.this, "onClose");
// 让图标晃动
// mHeaderImage.setTranslationX(translationX)
ObjectAnimator mAnim = ObjectAnimator.ofFloat(mHeaderImage, "translationX", 15.0f);
mAnim.setInterpolator(new CycleInterpolator(4));
mAnim.setDuration(500);
mAnim.start();
}
}); mLeftList.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, Cheeses.sCheeseStrings){//Cheeses自己定义的,存放的一些字符串
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
TextView mText = ((TextView)view);
mText.setTextColor(Color.WHITE);
return view;
}
}); mMainList.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, Cheeses.NAMES)); }
}
7.侧滑、ViewDragHelper、属性动画的更多相关文章
- Android动画效果之Property Animation进阶(属性动画)
前言: 前面初步认识了Android的Property Animation(属性动画)Android动画效果之初识Property Animation(属性动画)(三),并且利用属性动画简单了补间动画 ...
- Android动画效果之初识Property Animation(属性动画)
前言: 前面两篇介绍了Android的Tween Animation(补间动画) Android动画效果之Tween Animation(补间动画).Frame Animation(逐帧动画)Andr ...
- Android属性动画
这几天看郭神的博客 Android属性动画完全解析(上),初识属性动画的基本用法之后,我自己突然想实现一种动画功能,就是我们在携程网.阿里旅行等等手机APP端买火车票的时候,看到有选择城市,那么就有出 ...
- Android动画:模拟开关按钮点击打开动画(属性动画之平移动画)
在Android里面,一些炫酷的动画确实是很吸引人的地方,让然看了就赏心悦目,一个好看的动画可能会提高用户对软件的使用率.另外说到动画,在Android里面支持3种动画: 逐帧动画(Frame Ani ...
- android 帧动画,补间动画,属性动画的简单总结
帧动画——FrameAnimation 将一系列图片有序播放,形成动画的效果.其本质是一个Drawable,是一系列图片的集合,本身可以当做一个图片一样使用 在Drawable文件夹下,创建ani ...
- View动画和属性动画
在应用中, 动画效果提升用户体验, 主要分为View动画和属性动画. View动画变换场景图片效果, 效果包括平移(translate), 缩放(scale), 旋转(rotate), 透明(alph ...
- Android属性动画源代码解析(超详细)
本文假定你已经对属性动画有了一定的了解,至少使用过属性动画.下面我们就从属性动画最简单的使用开始. ObjectAnimator .ofInt(target,propName,values[]) .s ...
- ObjectAnimator属性动画应用demo
感谢慕课网--eclipse_xu 布局文件:activity_main.xml <FrameLayout xmlns:android="http://schemas.android. ...
- 使用属性动画 — Property Animation
属性动画,就是通过控制对象中的属性值产生的动画.属性动画是目前最高级的2D动画系统. 在API Level 11中添加.Property Animation号称能控制一切对象的动画,包括可见的和不可见 ...
随机推荐
- MySQL 的几种进入方式
对于码农一族来说,新入手电脑后,工具安装,环境搭建是为必备功课.环境搭不好工具装不全,后续的工作开展那就会有相当多的痛点,也会耗去相当多的时间和精力.近日,博主在安装 Mysql 数据库的过程中,一番 ...
- Kubernetes节点维护
1.设置节点为不可调度 kubectl cordon $NODENAME 2.使用kubectl drain优雅的结束节点上的所有 pod 并同时标记节点为不可调度 kubectl drain $NO ...
- 一个简单地template模板
之前的项目中用到了artTemplate模板,感觉挺有意思,于是查看相关资料,自己动手写了个简单地template模板插件.虽然会有一些不足,但也是自己的一番心血.主体代码如下 /* * 一个简单地t ...
- select 的选中问题
function bind(pageIndex) { if (getQueryString("_status") == "3") {//从首页中慢病管理人数进入 ...
- IDEA 错误: 找不到符号
在IDEA添加了一个新的Mapper.xml文件,然后删除,重新编译该文件所在的模块时,提示我编写的类找不到符合. 解决方法:右键.java文件,选择“complile ....”(ctrl+shif ...
- Chapter5_初始化与清理_this关键字
this关键字是Java中一类很特殊的关键字,首先它只能在方法内使用,用来表示调用这个方法的对象,在这一点上this和其他对对象的引用的操作是相同的.我们之所以可以在方法内部访问到它是因为编译器在方法 ...
- Maven学习 六 pom.xml文件
java jar包的搜索网址:http://mvnrepository.com/ pom作为项目对象模型.通过xml表示maven项目,使用pom.xml来实现.主要描述了项目:包括配置文件:开发者需 ...
- UWP作业(二)A Mobile App
看到给出的十个技术主题,我第一反应就是,对于大部分社交软件来讲,运用到全部这些都并不是什么难题,倒不如谈谈我手机里现有的社交软件中,使用时间最短但(you)是(dian)最(bu)好(hao)奇(yo ...
- linux从0开始----01
1.VMware 虚拟机安装与卸载 推荐安装较高版本,11.x以后的.本课程安装12.x版本,需要序列号. 2.在vmware中安装centos客户机.初学者选择典型安装也可以. 1.vware文件菜 ...
- temp--贵州银行
-------住宿----泊乐酒店----8905----与朱聿一起住 2018年 1月3日晚 1月4日晚 1月5日晚 1月6日晚 1月7日晚 1月8日晚 1月9日晚 已结清! ======= ...