安卓折叠视图
上一篇博客写的是侧滑菜单,这篇就写个会折叠的菜单,先上效果图:
好骚是不是= =
package xiaolin.widget;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;
import xiaolin.utils.Utils;
/**
* Created by Administrator on 2016/1/14.
*/
public class AFoldingPaneLayer extends ViewGroup {
public static final String TAG = "AFoldingPaneLayer";
public static final int MININERCEPTXVILOCITY = 8; // 最小拦截速度(dp)
public static final int MINTOUCHVILOCITY = 300; // 最小触发速度(dp)
private static final int drawerTime = 400; // 动画时间
private Scroller mScroller = null;
private VelocityTracker velocity = null;
private Paint paint = new Paint();
private Paint mPaintShadow = new Paint();
private Bitmap bitmapFull = null;
private AFoldingListener listener = null;
private int menuWidth = 0; // 菜单宽度
private boolean mIsOpenFolding = false; // 是否显示抽屉
private boolean isTouchRight = false;
private boolean isBeingDragged = false;
private boolean isUnableToDrad = false;
private boolean mIsUpdateBitmap = true;
private float lastInerceptX = 0;
private float lastInerceptY = 0;
private float lastTouchX = 0;
private float firstTouchX= 0;
private int mNumberOfFolds = 2;
public AFoldingPaneLayer(Context context) {
super(context);
init(context);
}
public AFoldingPaneLayer(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public AFoldingPaneLayer(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context){
mScroller = new Scroller(context);
paint.setAntiAlias(true);
paint.setDither(true);
mPaintShadow.setColor(Color.BLACK);
mPaintShadow.setStyle(Paint.Style.FILL);
}
@Override
// 布局测量
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width, height);
menuWidth= (int)(width*0.75f); // 菜单宽度
if(bitmapFull != null){
bitmapFull.recycle();
bitmapFull = null;
}
bitmapFull = Bitmap.createBitmap(width+menuWidth, height, Bitmap.Config.ARGB_8888);
mIsUpdateBitmap = true;
int childCount = getChildCount();
if(childCount < 1){
return;
}
View menuView = getChildAt(0);
// 合成菜单的测量宽度
int menuWidthSpec = MeasureSpec.makeMeasureSpec(menuWidth, MeasureSpec.EXACTLY);
menuView.measure(menuWidthSpec, heightMeasureSpec);
for(int i=1; i<childCount; i++){
View childView1 = getChildAt(i);
childView1.measure(widthMeasureSpec, heightMeasureSpec);
}
}
@Override
/*布局,设置字控件的位置*/
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
if(childCount <1){
return;
}
// 隐藏在左边的视图
View menuView = getChildAt(0);
// 设置子视图的位置,左上角坐标,宽高
menuView.layout(-menuView.getMeasuredWidth(), 0, 0, menuView.getMeasuredHeight());
int left = 0;
for(int i=1; i<childCount; i++) {
View childView = getChildAt(i);
int w = childView.getMeasuredWidth();
childView.layout(left, 0, left+w, childView.getMeasuredHeight());
left += w;
}
}
@Override
// onDraw()函数会调用此函数
public void computeScroll() {
if(mScroller.computeScrollOffset()){
int x = mScroller.getCurrX();
scrollTo(x, 0);
// 调用invalidate()无效
postInvalidate();
if(listener != null){
listener.progress(-x/menuWidth);
}
}
}
/**
* 矩阵变换
* @param rect_src 源
* @param rect_dst 目标
* @param deep 深度
* @param isScaleLeft 在左边还是在右边
* @return
*/
private Matrix calculate(Rect rect_src, Rect rect_dst, int deep, boolean isScaleLeft){
float src[] = new float[]{
rect_src.left, rect_src.top, // 左上
rect_src.right, rect_src.top, // 右上
rect_src.right, rect_src.bottom, // 右下
rect_src.left, rect_src.bottom // 左下
};
float[] dst = null;
if(isScaleLeft) {
dst = new float[]{
rect_dst.left, rect_dst.top+deep,
rect_dst.right, rect_dst.top,
rect_dst.right, rect_dst.bottom,
rect_dst.left, rect_dst.bottom-deep
};
}else{
dst = new float[]{
rect_dst.left, rect_dst.top,
rect_dst.right, rect_dst.top + deep,
rect_dst.right, rect_dst.bottom - deep,
rect_dst.left, rect_dst.bottom
};
}
Matrix matrix = new Matrix();
matrix.setPolyToPoly(src, 0, dst, 0, src.length >> 1);
return matrix;
}
/*绘制子视图*/
@Override
protected void dispatchDraw(Canvas canvas) {
int x = getScrollX();
if(x >= 0 || mNumberOfFolds==0){
super.dispatchDraw(canvas);
return;
}
canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG
| Paint.FILTER_BITMAP_FLAG));// 画布抗锯齿
if(-x == menuWidth || mIsUpdateBitmap){
bitmapFull.eraseColor(Color.argb(0, 0, 0, 0));
// 将所有像素设为透明
Canvas canvas1 = new Canvas(bitmapFull);
canvas1.translate(menuWidth, 0);
// 平移到正确位团置
super.dispatchDraw(canvas1);
// 把视图全部画到位图上
if(mIsUpdateBitmap){
mIsUpdateBitmap = false;
/*画上阴影*/
}
}
int height = getHeight();
if(-x != menuWidth) { /*以折叠形式绘制菜单*/
float dstX = x;
float dstW = Math.abs((float) x / mNumberOfFolds);
float srcX = 0;
float srcW = Math.abs((float) menuWidth / mNumberOfFolds);
int depth = (int)Math.sqrt(Math.pow(srcW, 2)-Math.pow(dstW, 2))/6;// 勾股定理,再缩小几倍
for (int i = 0; i < mNumberOfFolds; i++) {
Rect rect_src = new Rect((int) srcX, 0, (int) (srcX + srcW), height);
Rect rect_dst = new Rect((int) dstX, 0, (int) (dstX + dstW), height);
Matrix matrix = calculate(rect_src, rect_dst, depth, i%2==1);
canvas.save();
canvas.clipRect(rect_dst);
canvas.concat(matrix);
canvas.drawBitmap(bitmapFull,0, 0, paint);
if(i%2==1){
mPaintShadow.setAlpha(128-128*-x/menuWidth);
canvas.drawRect(rect_src, mPaintShadow);
}
canvas.restore();
dstX += dstW;
srcX += srcW;
}
}else{ /*直接绘制菜单*/
canvas.save();
canvas.clipRect(-menuWidth, 0, 0, height);
super.dispatchDraw(canvas);
canvas.restore();
}
/*绘制右边的主内容*/
Rect rect_src = new Rect(0, 0, this.getWidth(), height);
Rect rect_dst = new Rect(0, 0, this.getWidth()+x, height);
Matrix m = calculate(rect_src, rect_dst, -x / 8, false);
m.preTranslate(-menuWidth, 0);
canvas.save();
canvas.clipRect(rect_src);
canvas.drawBitmap(bitmapFull, m, paint);
canvas.restore();
}
/**
* 只要您在onInterceptTouchEvent方法中返回false,
* 每个后续的事件(从当前事件到最后ACTION_UP事件)将会先分发到onInterceptTouchEvent中,
* 然后再交给目标子控件的onTouchEvent处理 (前提是子控件的onTouchEvent返回是true )。
*
* 如果在onInterceptTouchEvent返回true,
* onInterceptTouchEvent方法中将不会收到后续的任何事件,
* 目标子控件中除了ACTION_CANCEL外也不会接收所有这些后续事件,
* 所有的后续事件将会被交付到你自己的onTouchEvent()方法中。
* @param event
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
if(action != 0){
if(isBeingDragged){
return true;
}
if(isUnableToDrad){
return false;
}
}
if(action == MotionEvent.ACTION_DOWN){
lastInerceptX = event.getX();
lastInerceptY = event.getY();
isBeingDragged = false;
isUnableToDrad = false;
if(!mScroller.isFinished()){
mScroller.abortAnimation();
return true;
}else if(mIsOpenFolding){
if(event.getX()>menuWidth){
isBeingDragged = true;
}
}
}else if(action == MotionEvent.ACTION_MOVE) {
float x = event.getX();
float y = event.getY();
float dx = Math.abs(x-lastInerceptX);
float dy = Math.abs(y-lastInerceptY);
if(dy>dx) { // 上下滑动
isUnableToDrad = true;
}else if(x<lastInerceptX && !mIsOpenFolding) { // 往左滑动&&已关窗
isUnableToDrad = true;
}else if(x>lastInerceptX && mIsOpenFolding){ // 往右滑动&&已开窗
isUnableToDrad = true;
}else if(dx>dy && dx> Utils.dpToPx(MININERCEPTXVILOCITY, getResources())){ // 往右并且大于一定速度
isBeingDragged = true;
}
lastInerceptX = x;
lastInerceptY = y;
}
return isBeingDragged;
}
// 触摸事件
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
float x = event.getX();
if(velocity == null){
mIsUpdateBitmap = true;
velocity = VelocityTracker.obtain();
lastTouchX = x;
if(mIsOpenFolding && x>menuWidth){
isTouchRight = true;
firstTouchX = x;
}else{
isTouchRight = false;
}
}
velocity.addMovement(event);
if(action == MotionEvent.ACTION_DOWN){// 可能无此动作
}else if(action == MotionEvent.ACTION_MOVE){ // 正在滑动
float dx = x-lastTouchX;
drawerViewBy((int)dx);
lastTouchX = x;
}else if(action == MotionEvent.ACTION_UP){
if(isTouchRight && firstTouchX==x){
closeFolding();
velocity.recycle();
velocity = null;
return true;
}else{
velocity.computeCurrentVelocity(1000);
if(velocity.getXVelocity() < -Utils.dpToPx(MINTOUCHVILOCITY, getResources())){
closeFolding();
}else if(velocity.getXVelocity() > Utils.dpToPx(MININERCEPTXVILOCITY, getResources())){
openFolding();
}else{
if(-getScrollX()>menuWidth/2){
openFolding();
}else{
closeFolding();
}
}
velocity.recycle();
velocity = null;
}
}
return true;
}
@Override
// 按键监听
public boolean dispatchKeyEvent(KeyEvent event) {
int code = event.getKeyCode();
int action = event.getAction();
if(code==KeyEvent.KEYCODE_BACK && action==KeyEvent.ACTION_UP){
if(mIsOpenFolding){
closeFolding();
return true;
}
}
return super.dispatchKeyEvent(event);
}
// 滚动距离并判断范围
private void drawerViewBy(int dx){
dx = -dx;
int x = getScrollX();
if(x+dx>0){
scrollTo(0, 0);
}else if(x+dx < -menuWidth){
scrollTo(-menuWidth, 0);
}else {
scrollTo(x + dx, 0);
}
if(listener != null){
listener.progress(-getScrollX()/(float)menuWidth);
}
}
// 设置窗格数量
public void setNumberOfFolds(int numberOfFolds){
this.mNumberOfFolds = numberOfFolds;
}
public int getNumberOfFolds(){
return mNumberOfFolds;
}
// 关闭菜单
public void closeFolding(){
int x = getScrollX();
mScroller.startScroll(x, 0, -x, 0, drawerTime);
invalidate();
mIsOpenFolding = false;
if(listener != null) {
listener.fold(mIsOpenFolding);
}
}
// 打开菜单
public void openFolding(){
int x = getScrollX();
mScroller.startScroll(x, 0, -menuWidth - x, 0, drawerTime);
invalidate();
mIsOpenFolding = true;
if(listener != null){
listener.fold(mIsOpenFolding);
}
}
// 是否打开菜单
public boolean isOpen(){
return mIsOpenFolding;
}
// 设置监听
public void setFoldingListener(AFoldingListener listener){
this.listener = listener;
}
// 监听接口
public interface AFoldingListener{
public void fold(boolean isOpen);
public void progress(float progress);// 0.0f-1.0f
}
}
package xiaolin.utils;
import android.content.res.Resources;
import android.util.TypedValue;
/**
* Created by Administrator on 2016/1/17.
*/
public class Utils {
private Utils(){
}
/**
* dp转为像素
* @param dp
* @param resources
* @return
*/
public static float dpToPx(float dp, Resources resources){
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.getDisplayMetrics());
}
}
在xml里使用
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#ffffff"
android:orientation="vertical">
<xiaolin.widget.AFoldingPaneLayer
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/layout">
<!-- 菜单部分 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/img_bg"
android:scaleType="centerCrop"/>
</LinearLayout>
<!-- 内容部分 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小林"
android:textColor="#ffffff"
android:textSize="20sp"/>
</LinearLayout>
</xiaolin.widget.AFoldingPaneLayer>
</LinearLayout>
在Activity里使用
package xiaolin.widget;
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AFoldingPaneLayer layer = (AFoldingPaneLayer)findViewById(R.id.layout);
layer.setNumberOfFolds(4);// 设置有四个窗格,默认有两个窗格
}
}