昨天刚弄完侧滑菜单的实现,就打算仿照一个易信的侧滑菜单来练练手,也顺便来展示一下框架的重要性~~
首先还是效果图:
其实实现起来很简单,只需要分别自定义左边的菜单布局文件和右边的内容布局文件即可,至于侧滑菜单的实现,可以看看上一篇文章,里面有详细解释,在这里,就不侧重讲解了,今天侧重于框架的使用~~
其中,对于易信圆形头像需要自定义一个ImageView来实现,这需要大家掌握,因为十分有用。
一、左边Menu的布局实现:
layout\menu.xml
layout\content.xml
layout\main.xml
"
SlidingLayout.java
package t.first;
import android.content.Context;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.widget.LinearLayout;
/**
* @ 对外仅需设置的接口
*
* -判断左侧布局是否完全显示出来,或完全隐藏,滑动过程中此值无效
* boolean SlidingLayout.isLeftLayoutVisible()
*
* -将屏幕滚动到右侧布局界面
* void SlidingLayout.scrollToRightLayout()
*
* -将屏幕滚动到左侧布局界面
* void SlidingLayout.scrollToLeftLayout()
*
*/
public class SlidingLayout extends LinearLayout {
private static final int SNAP_VELOCITY = 200; //滚动显示和隐藏左侧布局时,手指滑动需要达到的速度。
private VelocityTracker mVelocityTracker; //用于计算手指滑动的速度。
private int touchSlop; //在被判定为滚动之前用户手指可以移动的最大值。
private int screenWidth; //屏幕宽度值
private int leftEdge ; //左边最多可以滑动到的左边缘,值由左边布局的宽度来定,marginLeft到达此值之后,不能再减少。
private int rightEdge = 0; //左边最多可以滑动到的右边缘,值恒为0,即marginLeft到达0之后,不能增加。
private float xDown; //记录手指按下时的横坐标。
private float yDown; //记录手指按下时的纵坐标。
private float xMove; //记录手指移动时的横坐标。
private float yMove; //记录手指移动时的纵坐标。
private float xUp; //记录手机抬起时的横坐标。
private boolean isLeftLayoutVisible; //左侧布局当前是显示还是隐藏。只有完全显示或隐藏时才会更改此值,滑动过程中此值无效。
private boolean isSliding; //是否正在滑动。
private MarginLayoutParams leftLayoutParams; //左侧布局的参数,通过此参数来重新确定左侧布局的宽度,以及更改leftMargin的值。
private MarginLayoutParams rightLayoutParams; //右侧布局的参数,通过此参数来重新确定右侧布局的宽度。
private View leftLayout; //左侧布局对象。
private View rightLayout; //右侧布局对象。
//构造函数
public SlidingLayout(Context context, AttributeSet attrs)
{
super(context, attrs);
//获取屏幕宽度
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
screenWidth = wm.getDefaultDisplay().getWidth();
//获取滑动的最短距离
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
//创建VelocityTracker对象,并将触摸事件加入到VelocityTracker当中。
private void createVelocityTracker(MotionEvent event)
{
if (mVelocityTracker == null)
{
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
}
//拦截触摸事件
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch(event.getAction())
{
case MotionEvent.ACTION_DOWN:
xDown = event.getRawX();
yDown = event.getRawY();
//当左边菜单完全显示时,点击右边的View,我们希望是拦截,而左边不拦截
if(xDown > leftLayout.getLeft()+leftLayout.getWidth() && isLeftLayoutVisible)
return true;
break;
case MotionEvent.ACTION_MOVE:
int distanceX=(int) (event.getRawX()-xDown);
//水平滑动距离超过一定距离,拦截所有子view的touch事件,来处理自身onTouch事件来达到滑动的目的
if(Math.abs(distanceX)>=touchSlop)
{
return true;
}
break;
}
return false;
}
//触摸事件
@Override
public boolean onTouchEvent(MotionEvent event)
{
// TODO 自动生成的方法存根
createVelocityTracker(event);
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
// 手指按下时,记录按下时的横坐标
xDown = event.getRawX();
yDown = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
// 手指移动时,对比按下时的横坐标,计算出移动的距离,来调整右侧布局的leftMargin值,从而显示和隐藏左侧布局
xMove = event.getRawX();
yMove = event.getRawY();
int distanceX = (int) (xMove - xDown);
int distanceY = (int) (yMove - yDown);
//只有触摸右边的View,才发生滑动
if(xMove > leftLayout.getLeft()+leftLayout.getWidth())
{
//向右滑
if (!isLeftLayoutVisible && distanceX >= touchSlop && (isSliding || Math.abs(distanceY) <= touchSlop))
{
isSliding = true;
leftLayoutParams.leftMargin = leftEdge + distanceX;
if (leftLayoutParams.leftMargin > rightEdge)
{
leftLayoutParams.leftMargin = rightEdge;
}
}
//向左滑
else if (isLeftLayoutVisible && -distanceX >= touchSlop)
{
isSliding = true;
leftLayoutParams.leftMargin = distanceX;
if (leftLayoutParams.leftMargin < leftEdge)
{
leftLayoutParams.leftMargin = leftEdge;
}
}
leftLayout.setLayoutParams(leftLayoutParams);
}
break;
case MotionEvent.ACTION_UP:
xUp = event.getRawX();
int upDistanceX = (int) (xUp - xDown);
if (isSliding)
{
// 手指抬起时,进行判断当前手势的意图,从而决定是滚动到左侧布局,还是滚动到右侧布局
if (wantToShowLeftLayout())
{
if (shouldScrollToLeftLayout())
{
scrollToLeftLayout();
}
else
{
scrollToRightLayout();
}
}
else if (wantToShowRightLayout())
{
if (shouldScrollToRightLayout())
{
scrollToRightLayout();
}
else
{
scrollToLeftLayout();
}
}
}
//在左侧菜单完全显示时,我们希望的是点击右边的View可以发生恢复
else if (upDistanceX < touchSlop && isLeftLayoutVisible && xUp > leftLayout.getLeft()+leftLayout.getWidth())
{
scrollToRightLayout();
}
recycleVelocityTracker();
break;
}
if (this.isEnabled())
{
if (isSliding)
{
unFocusBindView();
return true;
}
if (isLeftLayoutVisible)
{
return true;
}
return false;
}
return true;
}
//将屏幕滚动到左侧布局界面,滚动速度设定为50.
public void scrollToLeftLayout()
{
//传入第一个参数
new ScrollTask().execute(50);
}
//将屏幕滚动到右侧布局界面,滚动速度设定为-50.
public void scrollToRightLayout()
{
//传入第一个参数
new ScrollTask().execute(-50);
}
//左侧布局是否完全显示出来,或完全隐藏,滑动过程中此值无效。
public boolean isLeftLayoutVisible()
{
return isLeftLayoutVisible;
}
//在onLayout中重新设定左侧布局和右侧布局的参数。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
super.onLayout(changed, l, t, r, b);
if (changed)
{
// 获取左侧布局对象
leftLayout = getChildAt(0);
leftLayoutParams = (MarginLayoutParams) leftLayout.getLayoutParams();
// 设置最左边距为负的左侧布局的宽度
leftEdge = -leftLayoutParams.width;
leftLayoutParams.leftMargin = leftEdge;
leftLayout.setLayoutParams(leftLayoutParams);
// 获取右侧布局对象
rightLayout = getChildAt(1);
rightLayoutParams = (MarginLayoutParams) rightLayout.getLayoutParams();
rightLayoutParams.width = screenWidth;
rightLayout.setLayoutParams(rightLayoutParams);
rightLayout.setClickable(true);
}
}
//判断当前手势的意图是不是想显示右侧布局。如果手指移动的距离是负数,且当前左侧布局是可见的,则认为当前手势是想要显示右侧布局。
private boolean wantToShowRightLayout()
{
return xUp - xDown < 0 && isLeftLayoutVisible;
}
//判断当前手势的意图是不是想显示左侧布局。如果手指移动的距离是正数,且当前左侧布局是不可见的,则认为当前手势是想要显示左侧布局。
private boolean wantToShowLeftLayout()
{
return xUp - xDown > 0 && !isLeftLayoutVisible;
}
//判断是否应该滚动将左侧布局展示出来。如果手指移动距离大于屏幕的1/2,或者手指移动速度大于SNAP_VELOCITY,就认为应该滚动将左侧布局展示出来。
private boolean shouldScrollToLeftLayout()
{
return xUp - xDown > leftLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY;
}
//判断是否应该滚动将右侧布局展示出来。如果手指移动距离加上leftLayoutPadding大于屏幕的1/2,或者手指移动速度大于SNAP_VELOCITY, 就认为应该滚动将右侧布局展示出来。
private boolean shouldScrollToRightLayout()
{
return xDown - xUp > leftLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY;
}
//获取手指在右侧布局的监听View上的滑动速度。
private int getScrollVelocity()
{
mVelocityTracker.computeCurrentVelocity(1000);
int velocity = (int) mVelocityTracker.getXVelocity();
return Math.abs(velocity);
}
//回收VelocityTracker对象。
private void recycleVelocityTracker()
{
mVelocityTracker.recycle();
mVelocityTracker = null;
}
//使用可以获得焦点的控件在滑动的时候失去焦点。
private void unFocusBindView()
{
if (rightLayout != null)
{
rightLayout.setPressed(false);
rightLayout.setFocusable(false);
rightLayout.setFocusableInTouchMode(false);
}
}
//*********************************************************************************************************************
/**
* @ 利用轻量级线程实现滑动 ,应用在手指松开的滑动动画
*/
class ScrollTask extends AsyncTask
{
//后台处理,入口参数对应第一个参数类型
@Override
protected Integer doInBackground(Integer... speed) {
int leftMargin = leftLayoutParams.leftMargin;
// 根据传入的速度来滚动界面,当滚动到达左边界或右边界时,跳出循环。
while (true)
{
leftMargin = leftMargin + speed[0];
if (leftMargin > rightEdge)
{
leftMargin = rightEdge;
break;
}
if (leftMargin < leftEdge)
{
leftMargin = leftEdge;
break;
}
//主动回调onProgressUpdate来更新界面(滑动)
publishProgress(leftMargin);
//使当前线程睡眠指定的毫秒数,为了要有滚动效果产生,每次循环使线程睡眠10毫秒,这样肉眼才能够看到滚动动画。
try
{
Thread.sleep(10);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
if (speed[0] > 0)
{
isLeftLayoutVisible = true;
}
else
{
isLeftLayoutVisible = false;
}
isSliding = false;
//返回对应第三个参数类型,并且把值传入onPostExecute
return leftMargin;
}
//调用publishProgress时,回调这个方法,用来更新界面(滑动),入口参数对应第二个参数类型
@Override
protected void onProgressUpdate(Integer... leftMargin)
{
leftLayoutParams.leftMargin = leftMargin[0];
leftLayout.setLayoutParams(leftLayoutParams);
//使用可以获得焦点的控件在滑动的时候失去焦点。
unFocusBindView();
}
//在doInBackground执行完成后执行界面更新,入口参数对应第三个参数类型
@Override
protected void onPostExecute(Integer leftMargin)
{
leftLayoutParams.leftMargin = leftMargin;
leftLayout.setLayoutParams(leftLayoutParams);
}
}
}
RoundedImageView.java
package t.first;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;
public class RoundedImageView extends ImageView{
//构造函数1
public RoundedImageView(Context context) {
super(context);
// TODO 自动生成的构造函数存根
}
//构造函数2
public RoundedImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
//构造函数3
public RoundedImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onDraw(Canvas canvas)
{
Drawable drawable = getDrawable();
//转换为32位ARGB位图
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap().copy(Bitmap.Config.ARGB_8888, true);
//得到该圆形位图
Bitmap roundBitmap = getCroppedBitmap(bitmap, getWidth());
canvas.drawBitmap(roundBitmap, 0, 0, null);
}
//转换圆形位图算法
public static Bitmap getCroppedBitmap(Bitmap bmp, int radius)
{
Bitmap bmp2;
if (bmp.getWidth() != radius || bmp.getHeight() != radius)
bmp2 = Bitmap.createScaledBitmap(bmp, radius, radius, false);
else
bmp2 = bmp;
//创建用于输出结果的32位位图
Bitmap output = Bitmap.createBitmap(bmp2.getWidth(), bmp2.getHeight(),Config.ARGB_8888);
//为输出的位图创建一个画布
Canvas canvas = new Canvas(output);
//创建画笔
Paint paint = new Paint();
//设置画笔属性
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
paint.setDither(true);
paint.setColor(Color.parseColor("#BAB399"));
//设置画布背景ARGB
canvas.drawARGB(0, 0, 0, 0);
//画一个圆作为底层
canvas.drawCircle(bmp2.getWidth() / 2 + 0.7f,bmp2.getHeight() / 2 + 0.7f, bmp2.getWidth() / 2 + 0.1f, paint);
//设置画笔规则,取两层绘制交集,显示上层。
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
//把原图画在已经画了圆形的画布上作为上层,取交集上层,则截取了圆形的原图
canvas.drawBitmap(bmp2, new Rect(0, 0, bmp2.getWidth(), bmp2.getHeight()), new Rect(0, 0, bmp2.getWidth(), bmp2.getHeight()), paint);
return output;
}
}
六、MainActivity.java
package t.first;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.TextView;
public class MainActivity extends Activity {
/** Called when the activity is first created. */
private RelativeLayout yixin;
private RelativeLayout friend;
private RelativeLayout map;
private RelativeLayout more;
private RelativeLayout setting;
private TextView title;
private ImageButton menu;
private SlidingLayout slidingLayout;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
slidingLayout = (SlidingLayout) findViewById(R.id.slidingLayout);
yixin = (RelativeLayout) findViewById(R.id.yixin);
friend = (RelativeLayout) findViewById(R.id.friend);
map = (RelativeLayout) findViewById(R.id.map);
more = (RelativeLayout) findViewById(R.id.more);
setting = (RelativeLayout) findViewById(R.id.setting);
title = (TextView) findViewById(R.id.ivTitleName);
menu = (ImageButton) findViewById(R.id.ivTitleBtnLeft);
yixin.setSelected(true);
yixin.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// TODO 自动生成的方法存根
yixin.setSelected(true);
friend.setSelected(false);
map.setSelected(false);
more.setSelected(false);
setting.setSelected(false);
title.setText("易信");
slidingLayout.scrollToRightLayout();
}
});
friend.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// TODO 自动生成的方法存根
yixin.setSelected(false);
friend.setSelected(true);
map.setSelected(false);
more.setSelected(false);
setting.setSelected(false);
title.setText("朋友圈");
slidingLayout.scrollToRightLayout();
}
});
map.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// TODO 自动生成的方法存根
yixin.setSelected(false);
friend.setSelected(false);
map.setSelected(true);
more.setSelected(false);
setting.setSelected(false);
title.setText("朋友地图");
slidingLayout.scrollToRightLayout();
}
});
more.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// TODO 自动生成的方法存根
yixin.setSelected(false);
friend.setSelected(false);
map.setSelected(false);
more.setSelected(true);
setting.setSelected(false);
title.setText("更多应用");
slidingLayout.scrollToRightLayout();
}
});
setting.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// TODO 自动生成的方法存根
yixin.setSelected(false);
friend.setSelected(false);
map.setSelected(false);
more.setSelected(false);
setting.setSelected(true);
title.setText("设置");
slidingLayout.scrollToRightLayout();
}
});
menu.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// TODO 自动生成的方法存根
if (slidingLayout.isLeftLayoutVisible())
{
slidingLayout.scrollToRightLayout();
}
else
{
slidingLayout.scrollToLeftLayout();
}
}
});
}
}
drawable\selector_slidingmenu_item.xml
八、左边菜单按钮选择器
drawable\selector_slidingmenu_btn.xml
drawable\selector_title_left.xml
drawable\selector_title_right.xml
value\styles.xml
十一、素材