月度归档: 2021 年 6 月

Android实现滑动的方法

下面通过一个例子来总结实现滑动的几种方式,例子的主要功能就是让我们的自定义View能够随着手指的移动而移动。
布局文件如下:
<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”match_parent”
android:layout_height=”match_parent”>
<com.scu.lly.dragviewtest.view.DragView
android:layout_width=”100dp”
android:layout_height=”100dp” />
</LinearLayout>

方式一:layout方法
在View进行绘制时,会调用onLayout()方法来设置显示的位置,因此,我们可以通过修改View的left、top、right、bottom四个属性来控制View的坐标。要控制View随手指滑动,因此需要在onTouchEvent()事件中进行滑动控制。代码如下:
public class DragView extends View{

private int mLastX;
private int mLastY;

public DragView(Context context) {
super(context);
init();
}

public DragView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

public DragView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}

private void init(){
setBackgroundColor(Color.BLUE);
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
int offsetX = x – mLastX;
int offsetY = y – mLastY;
//调整layout的四个坐标
layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY);
break;
}
return true;
}
}

方式二:offsetLeftAndRight()和offsetTopAndBottom()
这两个方法其实是对上面那种layout设置方式的封装、简化,在layout中,左left、右right两个方向都是加上offsetX,上top、下bottom两个方向都是加上offsetY,为了简化设置四个方向,Android提供了offsetLeftAndRight()来代替左右方向的设置,用offsetTopAndBottom()来代替上下方向的设置。
我们只需要修改上面代码ACTION_MOVE的部分,如下:
<span style=”white-space:pre”> </span>case MotionEvent.ACTION_MOVE:
int offsetX = x – mLastX;
int offsetY = y – mLastY;
//调整layout的四个坐标
//layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY);
//使用简写形式
offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);
break;

方式三:LayoutParams
LayoutParams保存了一个View的布局参数,因此我们可以通过动态改变LayoutParams中的布局参数来达到改变View的位置效果。通过getLayoutParams()方法来获取View的LayoutParams,这里获取到的LayoutParams需要根据View所在父布局的类型来设置不同的类型,比如,我们这个自定义View是放在LinearLayout中的,那么通过getLayoutParams()获取到的就是LinearLayout.LayoutParams。因此,通过getLayoutParams()获取到LayoutParams的前提就是这个View需要有一个父布局。
同样,我们只需要修改上面代码ACTION_MOVE的部分,如下:
case MotionEvent.ACTION_MOVE:
int offsetX = x – mLastX;
int offsetY = y – mLastY;
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) getLayoutParams();
lp.leftMargin = getLeft() + offsetX;
lp.topMargin = getTop() + offsetY;
setLayoutParams(lp);
break;
可以看到,通过LayoutParams改变一个View的位置时,改变的是这个View的Margin属性,这也是为什么这种方式一定要有父布局的原因,只有有了父布局,margin属性的设置才会起作用。
对于使用LayoutParams这种方式改变View位置,如果我们不想考虑父布局的类型,还可以使用ViewGroup.MarginLayoutParams来进行设置,这样也更加方便。如下:
case MotionEvent.ACTION_MOVE:
int offsetX = x – mLastX;
int offsetY = y – mLastY;
//LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) getLayoutParams();
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) getLayoutParams();
lp.leftMargin = getLeft() + offsetX;
lp.topMargin = getTop() + offsetY;
setLayoutParams(lp);
break;
效果是一样的。

方式四:scrollTo与scrollBy
关于scrollTo()和scrollBy()方法,我这篇文章《Android Scroller大揭秘》中有详细介绍。

使用scrollTo()和scrollBy()方法需要注意的一点是,scrollTo()和scrollBy()方法移动的是View的content,即让View的内容移动,如果在ViewGroup中使用scrollTo()和scrollBy()方法,那么移动的将是所有的子View,如果在View中使用,那么移动的将是View的内容。例如,TextView,content就是它的文本,ImageView,content就是它的drawable对象。

因此,上面例子中我们如果直接这样使用:
scrollBy(offsetX,offsetY);
发现View并没有移动,但其实是发生了移动的,只不过此时移动的是View中的内容,而我们例子中的content什么也没有。
所以,我们要想使这个View发生移动,我们就应该在View所在的ViewGroup中使用scrollBy或scrollTo方法来进行移动。同时,使用者两个方法进行移动的时候,注意此时的坐标方向与平常是相反的,具体在《Android Scroller大揭秘》有讲解。代码如下:
case MotionEvent.ACTION_MOVE:
//int offsetX = x – mLastX;
//int offsetY = y – mLastY;
//此时,计算坐标是相反的
int offsetX = mLastX – x;
int offsetY = mLastY – y;
//让View所在的ViewGroup进行移动
((View)getParent()).scrollBy(offsetX,offsetY);
break;

方式五:Scroller
通过Scroller这个辅助类,配合scrollTo和scrollBy可以实现一些更加高级的滑动效果,关于Scroller类的具体介绍,同样在这篇文章中有详解《Android Scroller大揭秘》。

这里,我们只是结合上面这个例子实现一个简单的功能,当我们滑动完毕抬起手指后,View自动回弹到原来的位置。代码如下:
public class DragView extends View{

private int mLastX;
private int mLastY;
private Scroller mScroller;

public DragView(Context context) {
super(context);
init(context);
}

public DragView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}

public DragView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}

private void init(Context context){
setBackgroundColor(Color.BLUE);
mScroller = new Scroller(context);
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
//int offsetX = x – mLastX;
//int offsetY = y – mLastY;
//此时,计算坐标是相反的
int offsetX = mLastX – x;
int offsetY = mLastY – y;
//让View所在的ViewGroup进行移动
((View)getParent()).scrollBy(offsetX,offsetY);
break;
case MotionEvent.ACTION_UP:
View viewGroup = (View) getParent();
mScroller.startScroll(viewGroup.getScrollX(),viewGroup.getScrollY(),-viewGroup.getScrollX(),-viewGroup.getScrollY());
//记住需要invalidate
invalidate();
break;
}
return true;
}

@Override
public void computeScroll() {
super.computeScroll();
if(mScroller.computeScrollOffset()){
((View)getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
//记住,需要不断调用invalidate进行重绘
invalidate();
}
}
}

以上五种方法就是常用的滑动View的方法。还有两种方式能够控制一个View的移动效果:属性动画和使用ViewDragHelper,对于这两种方法,大家可以查阅网上资料,就不详细介绍了。

(以上内容来源于看《Android群英传》的总结)
————————————————

Android实现滑动的几种方式

View的滑动对于View交互性及效果有很大影响,我们可以通过以下四种方式来实现View的滑动,准确地说是View位置的改变。要改变View的位置,首先我们需要了解Android的坐标系,因为View的是通过坐标来定位的。

*对坐标系

Android系统中,屏幕的*左上角为坐标原点,如下图所示。

%title插图%num

屏幕*左上角的点为坐标原点,向右向下分别为x轴和y轴

视图坐标系

视图坐标系是在View的层级体系中使用到的,View的父布局*左上角为坐标原点,向右向下为x轴和y轴,如下图所示:

%title插图%num

几个容易混淆的方法:

getX():视图坐标系点的X坐标
getRawX():*对坐标系点的X坐标
getLeft():视图坐标系View左边框距离ViewGroup左边框距离
getTranslationX():View的偏移量,初始为0,当View发生平移时,其值会变,向右为正,向左为负。
其中view.getX() = view.getLeft() + view.getTranslationX(),而get*Y同理。

1. 通过改变View的布局位置

View的layout方法用来将View放到布局的合适位置,我们可以通过这个方法改变它的left,top,right,bottom参数的值来改变它在布局中的位置。在此基础上,如果我们在用户手指移动的过程中不断地改变View的位置就可以让View跟随手指移动。要实现View跟随用户手指滑动,我们可以监听用户手指的动作(按下,移动,。。。)计算偏移量,通过layout改变View的位置即可。

如下代码则通过layout实现View跟随手指滑动(重写View的onTouchEvent方法)

private int lastX;
private int lastY;

@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.d(TAG,”onTouchEvent() down”);
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG,”onTouchEvent() move”);
int offX = x – lastX;
int offY = y -lastY;
layout(getLeft()+offX,getTop()+offY,getRight()+offX,getBottom()+offY);
break;
}
return true;
}

这里通过相对坐标计算偏移量完成View的滑动,还可以通过*对坐标计算偏移量,代码如下:

private int lastRawX;
private int lastRawY;

@Override
public boolean onTouchEvent(MotionEvent event) {
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.d(TAG,”onTouchEvent() down”);
lastRawX = rawX;
lastRawY = rawY;
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG,”onTouchEvent() move”);
int offX = rawX – lastRawX;
int offY = rawY -lastRawY;
layout(getLeft()+offX,getTop()+offY,getRight()+offX,getBottom()+offY);
lastRawX = rawX; // 重置坐标
lastRawY = rawY; // 重置坐标
break;
}
return true;
}

与相对坐标不同的是,使用*对坐标需要在move事件结束后重置上一次手指的坐标值,这样才能准确地计算出偏移量。

为什么*对坐标要重置?那是如果不重置的话每次移动都是拿新的坐标与*开始的坐标比较得到偏移量,而*开始的坐标是View的初始位置手指按下的坐标,View每次移动的偏移量应该是新位置的坐标减去上一次的坐标,所以每次移动后需要更新上一次的坐标。

除了使用View的layout方法重新布局View外,还可以使用offsetLeftAndRight和offsetTopAndBottom方法重新布局View,同样可以实现View的滑动效果。代码如下:

private int lastRawX;
private int lastRawY;

@Override
public boolean onTouchEvent(MotionEvent event) {
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.d(TAG,”onTouchEvent() down”);
lastRawX = rawX;
lastRawY = rawY;
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG,”onTouchEvent() move”);
int offX = rawX – lastRawX;
int offY = rawY -lastRawY;
offsetLeftAndRight(offX);
offsetTopAndBottom(offY);
// layout(getLeft()+offX,getTop()+offY,getRight()+offX,getBottom()+offY);
lastRawX = rawX;
lastRawY = rawY;
break;
}
return true;
}

还可以通过View的改变布局参数LayoutParams的leftMargin和topMargin属性值改变View的位置,实现View的滑动,代码如下:

private int lastRawX;
private int lastRawY;

@Override
public boolean onTouchEvent(MotionEvent event) {
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.d(TAG,”onTouchEvent() down”);
lastRawX = rawX;
lastRawY = rawY;
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG,”onTouchEvent() move”);
int offX = rawX – lastRawX;
int offY = rawY -lastRawY;
// offsetLeftAndRight(offX);
// offsetTopAndBottom(offY);
// layout(getLeft()+offX,getTop()+offY,getRight()+offX,getBottom()+offY);
ViewGroup.LayoutParams layoutParams = getLayoutParams();
((ViewGroup.MarginLayoutParams)layoutParams).leftMargin += offX;
((ViewGroup.MarginLayoutParams)layoutParams).topMargin += offY;
setLayoutParams(layoutParams);
lastRawX = rawX;
lastRawY = rawY;
break;
}
return true;
}

改变View的布局参数需要注意的是这个View(或ViewGroup)必须有一个父布局,同时还需要注意布局参数的类型(如LinearLayout.LayoutParams,FrameLayout.LayoutParams),不过可以使用万能的MarginLayoutParams,这样就可以不用考虑布局参数类型了。

2. 使用scrollTo和scrollBy

View类有scrollTo和scollBy方法,它们可以改变View内容的位置,scrollTo表示移动到某个坐标点,scrollBy表示移动多少偏移量。我们可以通过scrollBy实现View跟随手指的滑动,代码如下:

private int lastRawX;
private int lastRawY;

@Override
public boolean onTouchEvent(MotionEvent event) {
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.d(TAG,”onTouchEvent() down”);
lastRawX = rawX;
lastRawY = rawY;
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG,”onTouchEvent() move”);
int offX = rawX – lastRawX;
int offY = rawY -lastRawY;
// offsetLeftAndRight(offX);
// offsetTopAndBottom(offY);
// layout(getLeft()+offX,getTop()+offY,getRight()+offX,getBottom()+offY);
// ViewGroup.LayoutParams layoutParams = getLayoutParams();
// ((ViewGroup.MarginLayoutParams)layoutParams).leftMargin += offX;
// ((ViewGroup.MarginLayoutParams)layoutParams).topMargin += offY;
// setLayoutParams(layoutParams);
((View)getParent()).scrollBy(-offX,-offY);
lastRawX = rawX;
lastRawY = rawY;
break;
}
return true;
}

这里你可能会有所迷惑,为什么调用scrollBy的是View的父View(ViewGroup)?为什么偏移量是负的?

首先scrollBy移动的是View的内容content,而不是View本身,如TextView的content为文本,ImageView的content为drawable,而ViewGroup的content是View或是ViewGroup,所以要移动当前View本身,我们就需要通过它的ViewGroup改变自己的内容从而改变View本身的位置。其次,我们真正操作的是View的父控件ViewGroup,要让View往左(上/右/下)移,应该要让ViewGroup往相反方向移动,也就是右(下/左/上),所以偏移量就是相反的(负的)。下面贴上一张图,感受一下。

%title插图%num

3. 使用Scroller类实现View平滑移动

Android为View的滑动提供了Scroller辅助类,它本身并不能导致View滑动,需要借助computeScroll和ScrollTo方法完成View的滑动。使用Scroller类完成View的平滑,需要通过以下三个步骤:

(1)创建Scroller类

通常在自定义View的构造方法中完成Scroller类的初始化

mScroller = new Scroller(context);
1
(2)重写computeScroll方法

@Override
public void computeScroll() {
super.computeScroll();
// 判断Scroller滑动是否执行完毕
if(mScroller.computeScrollOffset()){
((View)getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
// 通过重绘让系统调用onDraw,onDraw中又会调用computeScroll,如此不断循环,直到Scroller执行完毕
invalidate();
}
}

这里需要注意的是computeScroll方法在onDraw中会被调用,因此需要调用invalidate方法通知View调用onDraw重绘,然后再调用computeScroll完成View的滑动,过程为invalidate->onDraw->computeScroll->invalidate->…,无限循环直到mScroller的computeScrollOffset返回false,也就是滑动完成。

(3)调用Scroller类的startScroll方法开启滚动过程

public void smoothScrollBy(int dx,int dy){
mScroller.startScroll(mScroller.getFinalX(),mScroller.getFinalY(),dx,dy,2000);
invalidate(); // 必须调用改方法通知View重绘以便computeScroll方法被调用。
}

接下来就开始模拟滑动过程了,重写onTouchEvent方法,代码如下:

@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_UP:
int offX = x – lastX;
int offY = y -lastY;
smoothScrollBy(-offX,-offY);
break;
}
return true;
}

计算偏移量的方法和上面一样,这里实现的效果是手指离开时,View会在2秒内平滑到手指离开时的位置。

4. 使用属性动画实现View的滑动

属性动画可以改变View的属性,那么我们可以通过属性动画改变View的x和y属性从而改变View的位置实现View的滑动,代码如下:

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void animationScroll(float dx, float dy){
Path path = new Path();
path.moveTo(dx,dy);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(this, “x”, “y”, path);
objectAnimator.start();
}

通过执行ObjectAnimator改变x和y属性,我们需要新的x和y属性值,可以通过重写onTouchEvent方法得到新的x和y属性值,代码如下:

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_UP:
int offX = x – lastX;
int offY = y -lastY;
animationScroll(getX()+offX,getY()+offY);
break;
}
return true;
}
————————————————

Android进阶之_实现滑动的7种方式详解

在android开发中,滑动对一个app来说,是非常重要的,流畅的滑动操作,能够给用户带来用好的体验,那么本次就来讲讲android中实现滑动有哪些方式。其实滑动一个View,本质上是移动一个View,改变其当前所属的位置,要实现View的滑动,就必须监听用户触摸的事件,且获取事件传入的坐标值,从而动画的改变位置而实现滑动。

android坐标系
首先要知道android的坐标系与我们平常学习的坐标系是不一样的,在android中是将左上方作为坐标原点,向右为x抽正方向,向下为y抽正方向,像在触摸事件中,getRawX(),getRawY()获取到的就是Android坐标中的坐标.

视图坐标系
android开发中除了上面的这种坐标以外,还有一种坐标,叫视图坐标系,他的原点不在是屏幕左上方,而是以父布局坐上角为坐标原点,像在触摸事件中,getX(),getY()获取到的就是视图坐标中的坐标.

触摸事件–MotionEvent
触摸事件MotionEvent在用户交互中,有非常重要的作用,因此必须要掌握他,我们先来看看Motievent中封装的一些常用的触摸事件常亮:

//单点触摸按下动作
public static final int ACTION_DOWN = 0;
//单点触摸离开动作
public static final int ACTION_UP = 1;
//触摸点移动动作
public static final int ACTION_MOVE = 2;
//触摸动作取消
public static final int ACTION_CANCEL = 3;
//触摸动作超出边界
public static final int ACTION_OUTSIDE = 4;
//多点触摸按下动作
public static final int ACTION_POINTER_DOWN = 5;
//多点触摸离开动作
public static final int ACTION_POINTER_UP = 6;

以上是比较常用的一些触摸事件,通常情况下,我们会在OnTouchEvent(MotionEvent event)方法中通过event.getAction()方法来获取触摸事件的类型,其代码模式如下:

@Override
public boolean onTouchEvent(MotionEvent event)
{
//获取当前输入点的坐标,(视图坐标)
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//处理输入按下事件
break;
case MotionEvent.ACTION_MOVE:
//处理输入的移动事件
break;
case MotionEvent.ACTION_UP:
//处理输入的离开事件
break;
}
return true; //注意,这里必须返回true,否则只能响应按下事件
}

以上只是一个空壳的架构,遇到的具体的场景,也有可能会新增多其他事件,或是用不到这么多事件等等,要根据实际情况来处理。在介绍如何实现滑动之前先来看看android中给我们提供了那些常用的获取坐标值,相对距离等的方法,主要是有以下两个类别:

View 提供的获取坐标方法

getTop(): 获取到的是View自身的顶边到其父布局顶边的距离

getBottom(): 获取到的是View自身的底边到其父布局顶边的距离

getLeft(): 获取到的是View自身的左边到其父布局左边的距离

getRight(): 获取到的是View自身的右边到其父布局左边的距离

MotionEvent提供的方法

getX(): 获取点击事件距离控件左边的距离,即视图坐标

getY(): 获取点击事件距离控件顶边的距离,即视图坐标

getRawX(): 获取点击事件距离整个屏幕左边的距离,即*对坐标

getRawY(): 获取点击事件距离整个屏幕顶边的距离,即*对坐标

介绍上面一些基本的知识点后,下面我们就来进入正题了,android中实现滑动的其中方法:

实现滑动的7种方法
其实不管是哪种滑动,他们的基本思路是不变的,都是:当触摸View时,系统记下当前的触摸坐标;当手指移动时,系统记下移动后的触摸点坐标,从而获得相对前一个点的偏移量,通过偏移量来修改View的坐标,并不断的更新,重复此动作,即可实现滑动的过程。
首先我们先来定义一个View,并置于LinearLayout中,我们的目的是要实现View随着我们手指的滑动而滑动,布局代码如下:

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical”>

<com.liaojh.scrolldemo.DragView
android:layout_width=”100dp”
android:layout_height=”100dp”
android:background=”#88ffffff”/>

</LinearLayout>

layout方法
我们知道,在进行View绘制时,会调用layout()方法来设置View的显示位置,而layout方法是通过left,top,right,bottom这四个参数来确定View的位置的,所以我们可以通过修改这四个参数的值,从而修改View的位置。首先我们在onTouchEvent方法中获取触摸点的坐标:

float x = event.getX();
float y = event.getY();

接着在ACTION_DOWN的时候记下触摸点的坐标值:

case MotionEvent.ACTION_DOWN:
//记录按下触摸点的位置
mLastX = x;
mLastY = y;
break;

*后在ACTION_MOVE的时候计算出偏移量,且将偏移量作用到layout方法中:

case MotionEvent.ACTION_MOVE:
//计算偏移量(此次坐标值-上次触摸点坐标值)
int offSetX = (int) (x – mLastX);
int offSetY = (int) (y – mLastY);

//在当前left,right,top.bottom的基础上加上偏移量
layout(getLeft() + offSetX,
getTop() + offSetY,
getRight() + offSetX,
getBottom() + offSetY
);
break;

这样每次在手指移动的时候,都会调用layout方法重新更新布局,从而达到移动的效果,完整代码如下:

package com.liaojh.scrolldemo;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

/**
* @author LiaoJH
* @DATE 15/11/7
* @VERSION 1.0
* @DESC TODO
*/
public class DragView extends View
{
private float mLastX;
private float mLastY;

public DragView(Context context)
{
this(context, null);
}

public DragView(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}

public DragView(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
}

@Override
public boolean onTouchEvent(MotionEvent event)
{
//获取当前输入点的坐标,(视图坐标)
float x = event.getX();
float y = event.getY();
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
//记录按下触摸点的位置
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
//计算偏移量(此次坐标值-上次触摸点坐标值)
int offSetX = (int) (x – mLastX);
int offSetY = (int) (y – mLastY);

//在当前left,right,top.bottom的基础上加上偏移量
layout(getLeft() + offSetX,
getTop() + offSetY,
getRight() + offSetX,
getBottom() + offSetY
);

break;
}
return true;
}
}

当然也可以使用getRawX(),getRawY()来获取*对坐标,然后使用*对坐标来更新View的位置,但要注意,在每次执行完ACTION_MOVE的逻辑之后,一定要重新设置初始坐标,这样才能准确获取偏移量,否则每次的偏移量都会加上View的父控件到屏幕顶边的距离,从而不是真正的偏移量了。

@Override
public boolean onTouchEvent(MotionEvent event)
{
//获取当前输入点的坐标,(*对坐标)
float rawX = event.getRawX();
float rawY = event.getRawY();
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
//记录按下触摸点的位置
mLastX = rawX;
mLastY = rawY;
break;
case MotionEvent.ACTION_MOVE:
//计算偏移量(此次坐标值-上次触摸点坐标值)
int offSetX = (int) (rawX – mLastX);
int offSetY = (int) (rawY – mLastY);

//在当前left,right,top.bottom的基础上加上偏移量
layout(getLeft() + offSetX,
getTop() + offSetY,
getRight() + offSetX,
getBottom() + offSetY
);

//重新设置初始位置的值
mLastX = rawX;
mLastY = rawY;
break;
}
return true;
}

offsetLeftAndRight()与offsetTopAndBottom()
这个方法相当于系统提供了一个对左右,上下移动的API的封装,在计算出偏移量之后,只需使用如下代码设置即可:

offsetLeftAndRight(offSetX);
offsetTopAndBottom(offSetY);

偏移量的计算与上面一致,只是换了layout方法而已。

LayoutParams
LayoutParams保存了一个View的布局参数,因此可以在程序中通过动态的改变布局的位置参数,也可以达到滑动的效果,代码如下:

LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) getLayoutParams();
lp.leftMargin = getLeft() + offSetX;
lp.topMargin = getTop() + offSetY;
setLayoutParams(lp);

使用此方式时需要特别注意:通过getLayoutParams()获取LayoutParams时,需要根据View所在的父布局的类型来设置不同的类型,比如这里,View所在的父布局是LinearLayout,所以可以强转成LinearLayout.LayoutParams。

在通过改变LayoutParams来改变View的位置时,通常改变的是这个View的Margin属性,其实除了LayoutParams之外,我们有时候还可以使用ViewGroup.MarginLayoutParams来改变View的位置,代码如下:

ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) getLayoutParams();
lp.leftMargin = getLeft() + offSetX;
lp.topMargin = getTop() + offSetY;
setLayoutParams(lp);
//使用这种方式的好处就是不用考虑父布局类型

scrollTo与scrollBy
在一个View中,系统提供了scrollTo与scrollBy两种方式来改变一个View的位置,其中scrollTo(x,y)表示移动到一个具体的坐标点(x,y),而scrollBy(x,y)表示移动的增量。与前面几种计算偏移量相同,使用scrollBy来移动View,代码如下:

scrollBy(offSetX,offSetY);

然后我们拖动View,发现View并没有移动,这是为杂呢?其实,方法没有错,view也的确移动了,只是他移动的不是我们想要的东西。scrollTo,scrollBy方法移动的是view的content,即让view的内容移动,如果是在ViewGroup中使用scrollTo,scrollBy方法,那么移动的将是所有的子View,而如果在View中使用的话,就是view的内容,所以我们需要改一下我们之前的代码:

((View)getParent()).scrollBy(offSetX, offSetY);

这次是可以滑动了,但是我们发现,滑动的效果跟我们想象的不一样,完全相反了,这又是为什么呢?其实这是因为android中对于移动参考系选择的不同从而实现这样的效果,而我们想要实现我们滑动的效果,只需将偏移量设置为负值即可,代码如下:

((View) getParent()).scrollBy(-offSetX, -offSetY);

同样的在使用*对坐标时,使用scrollTo也可以达到这样的效果。

scroller
如果让一个View向右移动200的距离,使用上面的方式,大家应该发现了一个问题,就是移动都是瞬间完成的,没有那种慢慢平滑的感觉,所以呢,android就给我们提供了一个类,叫scroller类,使用该类就可以实现像动画一样平滑的效果。

其实它实现的原理跟前面的scrooTo,scrollBy方法实现view的滑动原理类似,它是将ACTION_MOVE移动的一段位移划分成N段小的偏移量,然后再每一个偏移量里面使用scrollBy方法来实现view的瞬间移动,这样在整体的效果上就实现了平滑的效果,说白了就是利用人眼的视觉暂留特性。

下面我们就来实现这么一个例子,移动view到某个位置,松开手指,view都吸附到左边位置,一般来说,使用Scroller实现滑动,需经过以下几个步骤:

初始化Scroller

//初始化Scroller,使用默认的滑动时长与插值器
mScroller = new Scroller(context);

重写computeScroll()方法

该方法是Scroller类的核心,系统会在绘制View的时候调用draw()方法中调用该方法,这个方法本质上是使用scrollTo方法,通过Scroller类可以获取到当前的滚动值,这样我们就可以实现平滑一定的效果了,一般模板代码如下:

@Override
public void computeScroll()
{
super.computeScroll();
//判断Scroller是否执行完成
if (mScroller.computeScrollOffset()) {
((View)getParent()).scrollTo(
mScroller.getCurrX(),
mScroller.getCurrY()
);
//调用invalidate()computeScroll()方法
invalidate();
}
}

Scroller类提供中的方法:

computeScrollOffset(): 判断是否完成了真个滑动

getCurrX(): 获取在x抽方向上当前滑动的距离

getCurrY(): 获取在y抽方向上当前滑动的距离

startScroll开启滑动

*后在需要使用平滑移动的事件中,使用Scroller类的startScroll()方法来开启滑动过程,startScroller()方法有两个重载的方法:

– public void startScroll(int startX, int startY, int dx, int dy)

– public void startScroll(int startX, int startY, int dx, int dy, int duration)

可以看到他们的区别只是多了duration这个参数,而这个是滑动的时长,如果没有使用默认时长,默认是250毫秒,而其他四个坐标则表示起始坐标与偏移量,可以通过getScrollX(),getScrollY()来获取父视图中content所滑动到的点的距离,不过要注意这个值的正负,它与scrollBy,scrollTo中说的是一样的。经过上面这三步,我们就可以实现Scroller的平滑一定了。

继续上面的例子,我们可以在onTouchEvent方法中监听ACTION_UP事件动作,调用startScroll方法,其代码如下:

case MotionEvent.ACTION_UP:
//第三步
//当手指离开时,执行滑动过程
ViewGroup viewGroup = (ViewGroup) getParent();
mScroller.startScroll(
viewGroup.getScrollX(),
viewGroup.getScrollY(),
-viewGroup.getScrollX(),
0,
800
);
//刷新布局,从而调用computeScroll方法
invalidate();
break;

属相动画
使用属性动画同样可以控制一个View的滑动,下面使用属相动画来实现上边的效果(关于属相动画,请关注其他的博文),代码如下:

case MotionEvent.ACTION_UP:
ViewGroup viewGroup = (ViewGroup) getParent();
//属性动画执行滑动
ObjectAnimator.ofFloat(this, “translationX”, viewGroup.getScrollX()).setDuration(500)
.start();
break;

ViewDragHelper
一看这个类的名字,我们就知道他是与拖拽有关的,猜的没错,通过这个类我们基本可以实现各种不同的滑动,拖放效果,他是非常强大的一个类,但是它也是*为复杂的,但是不要慌,只要你不断的练习,就可以数量的掌握它的使用技巧。下面我们使用这个类来时实现类似于QQ滑动侧边栏的效果,相信广大朋友们多与这个现象是很熟悉的吧。

先来看看使用的步骤是如何的:

初始化ViewDragHelper

ViewDragHelper这个类通常是定义在一个ViewGroup的内部,并通过静态方法进行初始化,代码如下:

//初始化ViewDragHelper
viewDragHelper = ViewDragHelper.create(this,callback);

它的*个参数是要监听的View,通常是一个ViewGroup,第二个参数是一个Callback回调,它是整个ViewDragHelper的逻辑核心,后面进行具体介绍。

拦截事件

重写拦截事件onInterceptTouchEvent与onTouchEvent方法,将事件传递交给ViewDragHelper进行处理,代码如下:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev)
{
//2. 将事件交给ViewDragHelper
return viewDragHelper.shouldInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event)
{
//2. 将触摸事件传递给ViewDragHelper,不可少
viewDragHelper.processTouchEvent(event);
return true;
}

处理computeScroll()方法

前面我们在使用Scroller类的时候,重写过该方法,在这里我们也需要重写该方法,因为ViewDragHelper内部也是使用Scroller类来实现的,代码如下:

//3. 重写computeScroll
@Override
public void computeScroll()
{
//持续平滑动画 (高频率调用)
if (viewDragHelper.continueSettling(true))
// 如果返回true, 动画还需要继续执行
ViewCompat.postInvalidateOnAnimation(this);
}

处理回调Callback

通过如下代码创建一个Callback:

private ViewDragHelper.Callback callback = new ViewDragHelper.Callback()
{
@Override
//此方法中可以指定在创建ViewDragHelper时,参数ViewParent中的那些子View可以被移动
//根据返回结果决定当前child是否可以拖拽
// child 当前被拖拽的View
// pointerId 区分多点触摸的id
public boolean tryCaptureView(View child, int pointerId)
{
//如果当前触摸的view是mMainView时开始检测
return mMainView == child;
}

@Override
//水平方向的滑动
// 根据建议值 修正将要移动到的(横向)位置 (重要)
// 此时没有发生真正的移动
public int clampViewPositionHorizontal(View child, int left, int dx)
{
//返回要滑动的距离,默认返回0,既不滑动
//参数参考clampViewPositionVertical
f (child == mMainView)
{
if (left > 300)
{
left = 300;
}
if (left < 0)
{
left = 0;
}
}
return left;
}

@Override
//垂直方向的滑动
// 根据建议值 修正将要移动到的(纵向)位置 (重要)
// 此时没有发生真正的移动
public int clampViewPositionVertical(View child, int top, int dy)
{
//top : 垂直向上child滑动的距离,
//dy: 表示比较前一次的增量,通常只需返回top即可,如果需要精确计算padding等属性的话,就需要对left进行处理
return super.clampViewPositionVertical(child, top, dy); //0
}
};

到这里就可以拖拽mMainView移动了。

下面我们继续来优化这个代码,还记得之前我们使用Scroller时,当手指离开屏幕后,子view会吸附到左边位置,当时我们监听ACTION_UP,然后调用startScroll来实现的,这里我们使用ViewDragHelper来实现。

在ViewDragHelper.Callback中,系统提供了这么一个方法—onViewReleased(),我们可以通过重写这个方法,来实现之前的操作,当然这个方法内部也是通过Scroller来实现的,这也是为什么我们要重写computeScroll方法的原因,实现代码如下:

@Override
//拖动结束时调用
public void onViewReleased(View releasedChild, float xvel, float yvel)
{
if (mMainView.getLeft() < 150)
{
// 触发一个平滑动画,关闭菜单,相当于Scroll的startScroll方法
if (viewDragHelper.smoothSlideViewTo(mMainView, 0, 0))
{
// 返回true代表还没有移动到指定位置, 需要刷新界面.
// 参数传this(child所在的ViewGroup)
ViewCompat.postInvalidateOnAnimation(DragLayout.this);
}
}
else
{
//打开菜单
if (viewDragHelper.smoothSlideViewTo(mMainView, 300, 0)) ;
{
ViewCompat.postInvalidateOnAnimation(DragLayout.this);
}
}
super.onViewReleased(releasedChild, xvel, yvel);
}

 

当滑动的距离小于150时,mMainView回到原来的位置,当大于150时,滑动到300的位置,相当于打开了mMenuView,而且滑动的时候是很平滑的。此外还有一些方法:

@Override
public void onViewCaptured(View capturedChild, int activePointerId)
{
// 当capturedChild被捕获时,调用.
super.onViewCaptured(capturedChild, activePointerId);
}

@Override
public int getViewHorizontalDragRange(View child)
{
// 返回拖拽的范围, 不对拖拽进行真正的限制. 仅仅决定了动画执行速度
return 300;
}

@Override
//当View位置改变的时候, 处理要做的事情 (更新状态, 伴随动画, 重绘界面)
// 此时,View已经发生了位置的改变
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy)
{
// changedView 改变位置的View
// left 新的左边值
// dx 水平方向变化量
super.onViewPositionChanged(changedView, left, top, dx, dy);
}

说明:里面还有很多关于处理各种事件方法的定义,如:

onViewCaptured():用户触摸到view后回调

onViewDragStateChanged(state):这个事件在拖拽状态改变时回调,比如:idle,dragging等状态

onViewPositionChanged():这个是在位置改变的时候回调,常用于滑动时伴随动画的实现效果等

对于里面的方法,如果不知道什么意思,则可以打印log,看看参数的意思。

总结
这里介绍的就是android实现滑动的七种方法,至于使用哪一种好,就要结合具体的项目需求场景了,毕竟硬生生的实现这个效果,而不管用户的使用体验式不切实际的,这里面个人觉得比较重要的是Scroller类的使用。属性动画以及ViewDragHelper类,特别是*后一个,也是*难*复杂的,但也是甩的*多的。

终于写完了,好累的赶脚~~~
————————————————

android 使用命令模拟点击 滑动

adb shell getevent -p

%title插图%num

出现上述截图的就是屏幕触摸输入设备

带上 -l

adb shell getevent -p -l

%title插图%num

%title插图%num

获得event 体系里 宽(0035)和高(0036)

通过adb模拟滑动、按键、点击事件

adb shell input -h

%title插图%num

adb shell input text string 在搜索框中自动输入 string

keyevent 手机的按键操作,如:home键、返回键、菜单键、锁屏等

例如需要点击一下home键

adb shell input keyevent 3 或者 adb shell input keyevent KEYCODE_HOME

tap 是模拟touch屏幕的事件,以下命令就是点击屏幕300,300位置

adb shell input tap 300 300

swipe 是滑动事件,以下命令表示从屏幕300,300 移动到400,400

adb shell input swipe 300 300 400 400

KEYCODE_HOME 所在文件

frameworks/base/core/java/android/view/KeyEvent.java

sendevent 命令

sendevent /dev/input/eventX type code value

type、code、value 定义在kernel-3.18/include/uapi/linux/input.h

/*

* Event types
*/

#define EV_SYN 0x00
#define EV_KEY 0x01 –按键
#define EV_REL 0x02 –触摸相对坐标
#define EV_ABS 0x03 –触摸*对坐标
#define EV_MSC 0x04
#define EV_SW 0x05
#define EV_LED 0x11
#define EV_SND 0x12
#define EV_REP 0x14
#define EV_FF 0x15
#define EV_PWR 0x16
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)

一般常用的是EV_KEY, EV_REL, EV_ABS, EV_SYN

分别对应keyboard, 相对坐标, *对坐标, 同步事件

EV_SYN对应的code如下

/*
* Synchronization events.
*/

#define SYN_REPORT 0
#define SYN_CONFIG 1
#define SYN_MT_REPORT 2
#define SYN_DROPPED 3
#define SYN_MAX 0xf
#define SYN_CNT (SYN_MAX+1)

input keyevent

模拟按键,sendevent用起来比较繁琐,可以用input keyevent代替

input keyevent 3 // Home (KeyEvent.java)

input keyevent 4 // Back

input keyevent 19 //Up

用senevent 模拟触屏事件

sendevent /dev/input/event1 0003 0000 0000015e // ABS x 坐标

sendevent /dev/input/event1: 0003 0001 000000df // ABS y 坐标

sendevent /dev/input/event1: 0001 014a 00000001 // BTN touch事件 值为1

adb shell input keyevent 82 这个命令会发送一个解屏事件到锁屏的设备上解屏

http://blog.shvetsov.com/2013/02/grab-android-screenshot-to-computer-via.html

adb shell screencap -p | perl -pe ‘s/\x0D\x0A/\x0A/g’ > screen.png 截屏并通过perl输出到本地目录

————————————————

Android实现滑动的几种方式演示

一、前言
*近闲来无事,也不知道研究点什么比较好。就买了几本书,加强基础。这编博客是从徐宜生的Android群英传中总结而来的,非常好的一本书,推荐大家入手。

我将用这几种方式,实现一个可拖动的View。

二、 layout方式
我们都知道View绘制流程的主要三个步骤,onMeaure测量 -onLayout摆放-onDraw绘制。关于这方面的博文太多太多,我也就不再多说。

layout可以控制View在父布局中的摆放位置。
我们只需要监听View的事件,然后一直调用layout方法,让View跟随手指的位移即可。

/**
* Created by AItsuki on 2016/3/1.
*/
public class SlideByLayoutView extends View {

private int startX;
private int startY;

public SlideByLayoutView(Context context, AttributeSet attrs) {
super(context, attrs);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
startX = (int) event.getX();
startY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
int endX = (int) event.getX();
int endY = (int) event.getY();
int dx = endX- startX;
int dy = endY – startY;
layout(getLeft() + dx, getTop() + dy, getRight() + dx, getBottom() + dy);
break;
}
return true;
}
}

%title插图%num
三、scrollTo和scrollBy
scrollTo(x,y):移动到具体位置
scrollBy(dx,dy):移动增量,相对于自身位置。
将二中的layout(getLeft() + dx, getTop() + dy, getRight() + dx, getBottom() + dy);替换成scrollBy(dx,dy)
会发现不能拖动方块,因为scroll移动的是View的内容,而不是自身。所以在使用scroll的时候,我们应该移动父布局的内容。

/**
* Created by AItsuki on 2016/3/1.
*/
public class SlideByScroll extends View {

private int startX;
private int startY;

public SlideByScroll(Context context, AttributeSet attrs) {
super(context, attrs);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int endX = (int) event.getRawX();
int endY = (int) event.getRawY();
int dx = endX – startX;
int dy = endY – startY;
// layout(getLeft() + dx, getTop() + dy, getRight() + dx, getBottom() + dy);
View parent = (View) getParent();
parent.scrollBy(dx, dy);

startX = endX;
startY = endY;
break;
}
return true;
}
}

注意这里不能使用getX(), 而需要获取getRawX()。因为getX是获取View自身内部的坐标,我们需要移动的是父布局,所以我们应该获取屏幕的坐标。

然后我们再次运行会发现,方块居然是反方向运行的。

 

%title插图%num

 

但是,为什么会这样呢?
因为scrollBy滚动的手机屏幕,看下面这张图给就可以很好的理解了(黑框请看作手机屏幕)。

%title插图%num

解决方式就是:在dx和dy前面加个负号就行了,parent.scrollBy(-dx, -dy);

3.1 Scroller
既然说到了scrollBy和scrollTo,那么这里就不能说一下scroller了。

如果我想实现这么个效果,当我点击方块的时候,让他平滑滚动一段距离,我们应该怎么做呢?
google提供了一个很方便的东西,那就是scroller。虽然属性动画也可以,但是我们现在就来说说scroller。

scroller是什么?
scroller可以模拟一个滚动过程,它有两个方法开启模拟效果。

public void startScroll(int startX, int startY, int dx, int dy)
public void startScroll(int startX, int startY, int dx, int dy, int duration)

其实scroller并没有作用于View,它只是模拟了一个过程而已。
实际滚动其实还需要我们自己调用scrollTo方法。

/**
* Created by AItsuki on 2016/3/1.
*/
public class ScrollerDemo extends View implements View.OnClickListener {

private final Scroller mScroller;

public ScrollerDemo(Context context, AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
setOnClickListener(this);
}

// computerScroll方法会在invalidate执行的时候调用。
@Override
public void computeScroll() {
super.computeScroll();
// 判断scroller是否执行完毕
if(mScroller.computeScrollOffset()) {
((View)getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}

@Override
public void onClick(View v) {
View parent = (View) getParent();
mScroller.startScroll(parent.getScrollX(),parent.getScrollY(),-100,-100);
invalidate();
}
}

%title插图%num

四、属性动画
/**
* Created by AItsuki on 2016/3/2.
*/
public class SlideByAnimator extends View {

private float startX;
private float startY;

public SlideByAnimator(Context context, AttributeSet attrs) {
super(context, attrs);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
startX = event.getRawX();
startY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float endX = event.getRawX();
float endY = event.getRawY();
float dx = endX – startX;
float dy = endY – startY;

ObjectAnimator.ofFloat(this, “translationX”, getTranslationX() + dx).setDuration(0).start();
ObjectAnimator.ofFloat(this, “translationY”, getTranslationY() + dy).setDuration(0).start();

startX = endX;
startY = endY;
break;
}
return true;
}

}

虽然属性动画会改变控件的位置,并且能获取到点击事件。
但是,实际上View的位置并没有改变,通过getLeft(),getTop()等方法获取的值也未曾改变。
属性动画会把你位移过的距离保存起来,所以可以通过getleft()+getTranslationX()获取到当前View显示的准确位置。

五、ViewDragHelper
ViewDragHelper是一个非常强大的类,可以帮我们处理复杂的拖动逻辑。
Android自带的侧边栏就用到了这个类,在这里我们也用它实现一个简单的侧边栏。

<?xml version=”1.0″ encoding=”utf-8″?>
<com.aitsuki.slidedemo.SimpleDrawerLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
tools:context=”.MainActivity”>

<LinearLayout
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:background=”#000″>

<TextView
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_gravity=”center_vertical”
android:text=”侧边栏”
android:textColor=”#fff”
android:textSize=”30dp” />

</LinearLayout>

<FrameLayout
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:background=”#fff”>

<TextView
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_gravity=”center”
android:text=”主页面”
android:textColor=”#000″
android:textSize=”30dp” />

</FrameLayout>
</com.aitsuki.slidedemo.SimpleDrawerLayout>

 

/**
* Created by AItsuki on 2016/3/3.
*/
public class SimpleDrawerLayout extends FrameLayout {

private final ViewDragHelper mDragHelper;
private View mContent;
private int mMenuWidth;

public SimpleDrawerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mDragHelper = ViewDragHelper.create(this, mCallBack);
}

@Override
protected void onFinishInflate() {
super.onFinishInflate();
mContent = getChildAt(1);
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mMenuWidth = w / 3 * 2;
}

@Override
public boolean onTouchEvent(MotionEvent event) {
mDragHelper.processTouchEvent(event);
return true;
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mDragHelper.shouldInterceptTouchEvent(ev);
}

private ViewDragHelper.Callback mCallBack = new ViewDragHelper.Callback() {

// 触摸到View的时候就会回调这个方法。
// return true表示抓取这个View。
@Override
public boolean tryCaptureView(View child, int pointerId) {
return mContent == child;
}

@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {

return left > 0 ? left > mMenuWidth ? mMenuWidth : left : 0; // 只能右划出菜单,并且菜单*大宽度为屏幕3分之2
}

@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);

if (xvel > 300) {
open();
} else if (xvel < -300) {
close();
} else {
if (mContent.getLeft() > mMenuWidth / 2) {
open();
} else {
close();
}
}
}
};

private void close() {
mDragHelper.smoothSlideViewTo(mContent, 0, 0);
invalidate();
}

private void open() {
mDragHelper.smoothSlideViewTo(mContent, mMenuWidth, 0);
invalidate();
}

@Override
public void computeScroll() {
super.computeScroll();
if (mDragHelper.continueSettling(true)) {
invalidate();
}
}
}
%title插图%num

————————————————

Android实现滑动的四种方式

效果展示

%title插图%num
做Android自定义View开发,实现布局的滑动是必不可少的一项手艺。下面我为大家准备了五种移动布局的方法:

layout 法 CoordinatorLayout原理的简析
LayoutParams 法 CoordinatorLayout原理的简析
scrollTo/scrollBy
Scroller 类
ViewDragHelper 类 : ViewDragHelper 类
activity_main.xml布局文件

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/res-auto”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical”
tools:context=”.MainActivity”>

<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width=”match_parent”
android:layout_height=”match_parent”>

<com.wust.SelfCoordinator.CanDragView
android:layout_width=”100dp”
android:layout_height=”100dp”
android:background=”#f00″/>
<TextView
android:layout_width=”100dp”
android:layout_height=”100dp”
android:background=”#0f0″
android:layout_marginTop=”200dp”/>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

</LinearLayout>
这里面存在 CoordinatorLayout 这个布局,是因为我准备写 CoordinatorLayout 布局的教程的,Android滑动方式只是个小插曲,所以,布局文件可以不用按照我的写,仅供参考。

1、layout 法

自定义View的 java 代码

package com.wust.SelfCoordinator;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.Nullable;

/**
* ClassName: CanDragView <br/>
* Description: <br/>
* date: 2021/6/29 9:45<br/>
*
* @author yiqi<br />
* @QQ 1820762465
*/
public class CanDragView extends View {

int startX;
int startY;

public CanDragView(Context context) {
this(context,null);
}

public CanDragView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}

public CanDragView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
{
startX =(int) event.getX();
startY =(int) event.getY();
}
break;
case MotionEvent.ACTION_MOVE:
{
int endX = (int) event.getX();
int endY = (int) event.getY();
int moveX = endX – startX;
int moveY = endY – startY;
System.out.println(“moveX ->” + moveX + “moveY ->” + moveY);
//移动布局的关键性代码
layout(getLeft()+moveX,getTop()+moveY,getRight()+moveX,getBottom()+moveY);
}
break;
}
return true;
}

}
2、LayoutParams 法

package com.wust.SelfCoordinator;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;

import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;

/**
* ClassName: CanDragView <br/>
* Description: <br/>
* date: 2021/6/29 9:45<br/>
*
* @author yiqi<br />
* @QQ 1820762465
*/
public class CanDragView extends View {

int startX;
int startY;

public CanDragView(Context context) {
this(context,null);
}

public CanDragView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}

public CanDragView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
{
startX =(int) event.getX();
startY =(int) event.getY();
}
break;
case MotionEvent.ACTION_MOVE:
{
int endX = (int) event.getX();
int endY = (int) event.getY();
int moveX = endX – startX;
int moveY = endY – startY;
System.out.println(“moveX ->” + moveX + “moveY ->” + moveY);
//移动布局的关键性代码,如果布局已经在 xml 文件中存在,你直接用 getLayoutParams() 获取参数信息就可以了,不要在重新 new
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) getLayoutParams();
params.leftMargin = getLeft()+moveX;
params.topMargin = getTop()+moveY;
setLayoutParams(params);
}
break;
}
return true;
}

}
3、scrollTo/scrollBy

package com.wust.SelfCoordinator;

import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;

/**
* ClassName: CanDragView <br/>
* Description: <br/>
* date: 2021/6/29 9:45<br/>
*
* @author yiqi<br />
* @QQ 1820762465
*/
@SuppressLint(“AppCompatCustomView”)
public class CanDragView extends TextView {

int startX;
int startY;

public CanDragView(Context context) {
this(context,null);
}

public CanDragView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}

public CanDragView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
{
startX =(int) event.getX();
startY =(int) event.getY();
}
break;
case MotionEvent.ACTION_MOVE:
{
int endX = (int) event.getX();
int endY = (int) event.getY();
int moveX = endX – startX;
int moveY = endY – startY;
System.out.println(“moveX ->” + moveX + “moveY ->” + moveY);
//移动布局的关键性代码,scrollTo/scrollBy 移动的是 View中的内容 ViewGroup中的子View
scrollTo(-(getLeft()+moveX),-(getTop()+moveY));
invalidate();
}
break;
}
return true;
}

}
需要注意的两点:

这个方法移动的 布局 的内容,不是自己
得加负号,大家自己尝试一下就明白了

%title插图%num
Scroller 类

原理大家可以参考这篇文章 : Scroller与computeScroll处理滑动

package com.wust.SelfCoordinator;

import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Scroller;
import android.widget.TextView;

import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;

/**
* ClassName: CanDragView <br/>
* Description: <br/>
* date: 2021/6/29 9:45<br/>
*
* @author yiqi<br />
* @QQ 1820762465
*/
@SuppressLint(“AppCompatCustomView”)
public class CanDragView extends TextView {

int startX;
int startY;
private Scroller scroller;
private int moveX;
private int moveY;

public CanDragView(Context context) {
this(context,null);
}

public CanDragView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}

public CanDragView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//*步:创建 scroller 对象
scroller = new Scroller(context);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
{
startX =(int) event.getX();
startY =(int) event.getY();
}
break;
case MotionEvent.ACTION_MOVE:
{
int endX = (int) event.getX();
int endY = (int) event.getY();
moveX = endX – startX;
moveY = endY – startY;
System.out.println(“moveX ->” + moveX + “moveY ->” + moveY);
//移动布局的关键性代码,scrollTo/scrollBy 移动的是 View中的内容 ViewGroup中的子View
//第二步:开始滚动
scroller.startScroll(getLeft(),getTop(), moveX, moveY);
//这个不能少,因为这个会调用 computeScroll()
invalidate();
}
break;
}
return true;
}

@Override
public void computeScroll() {
//第三步:编写 computeScroll,反复矫正
if (scroller.computeScrollOffset()){
scrollTo(getLeft()+moveX,getTop()+moveY);
invalidate();
}
super.computeScroll();
}
}
这个方法也是移动内容,和 scrollTo / scrollBy 一样。

ViewDragHelper 类
————————————————

2021年6月中国采购经理指数运行情况

国家统计局服务业调查中心中国物流与采购联合会   一、中国制造业采购经理指数运行情况   6月份,中国制造业采购经理指数(PMI)为50.9%,较上月微落0.1个百分点,继续位于临界点以上,制造业延续稳定扩张态势。    从企业规模看,大、中型企业PMI分别为51.7%和50.8%,比上月回落0.1和0.3个百分点,均高于临界点;小型企业PMI为49.1%,比上月回升0.3个百分点,低于临界点。   从分类指数看,在构成制造业PMI的5个分类指数中,生产指数和新订单指数均高于临界点,原材料库存指数、从业人员指数和供应商配送时间指数均低于临界点。   生产指数为51.9%,比上月回落0.8个百分点,高于临界点,表明制造业生产保持扩张,但步伐有所放慢。   新订单指数为51.5%,比上月上升0.2个百分点,高于临界点,表明制造业市场需求继续增长。   原材料库存指数为48.0%,比上月回升0.3个百分点,低于临界点,表明制造业主要原材料库存量降幅收窄。   从业人员指数为49.2%,比上月回升0.3个百分点,低于临界点,表明制造业企业用工景气度较上月小幅改善。   供应商配送时间指数为47.9%,虽比上月回升0.3个百分点,但低于临界点,表明制造业原材料供应商交货时间继续放慢。 表1 中国制造业PMI及构成指数(经季节调整)                                              单位:%   PMI   生产 新订单 原材料 库存 从业人员 供应商配送时间 2020年6月 50.9 53.9 51.4 47.6 49.1 50.5 2020年7月 51.1 54.0 51.7 47.9 49.3 50.4 2020年8月 51.0 53.5 52.0 47.3 49.4 50.4 2020年9月 51.5 54.0 52.8 48.5 49.6 50.7 2020年10月 51.4 53.9 52.8 48.0 49.3 50.6 2020年11月 52.1 54.7 53.9 48.6 49.5 50.1 2020年12月 51.9 54.2 53.6 48.6 49.6 49.9 2021年1月 51.3 53.5 52.3 49.0 48.4 48.8 2021年2月 50.6 51.9 51.5 47.7 48.1 47.9 2021年3月 51.9 53.9 53.6 48.4 50.1 50.0 2021年4月 51.1 52.2 52.0 48.3 49.6 48.7 2021年5月 51.0 52.7 51.3 47.7 48.9 47.6 2021年6月 50.9 51.9 51.5 48.0 49.2 47.9  表2 中国制造业PMI其他相关指标情况(经季节调整)                                              单位:%   新出口 订单 进口 采购量 主要原材料购进价格 出厂 价格 产成品 库存 在手 订单 生产经营活动预期 2020年6月 42.6 47.0 51.8 56.8 52.4 46.8 44.8 57.5 2020年7月 48.4 49.1 52.4 58.1 52.2 47.6 45.6 57.8 2020年8月 49.1 49.0 51.7 58.3 53.2 47.1 46.0 58.6 2020年9月 50.8 50.4 53.6 58.5 52.5 48.4 46.1 58.7 2020年10月 51.0 50.8 53.1 58.8 53.2 44.9 47.2 59.3 2020年11月 51.5 50.9 53.7 62.6 56.5 45.7 46.7 60.1 2020年12月 51.3 50.4 53.2 68.0 58.9 46.2 47.1 59.8 2021年1月 50.2 49.8 52.0 67.1 57.2 49.0 47.3 57.9 2021年2月 48.8 49.6 51.6 66.7 58.5 48.0 46.1 59.2 2021年3月 51.2 51.1 53.1 69.4 59.8 46.7 46.6 58.5 2021年4月 50.4 50.6 51.7 66.9 57.3 46.8 46.4 58.3 2021年5月 48.3 50.9 51.9 72.8 60.6 46.5 45.9 58.2 2021年6月 48.1 49.7 51.7 61.2 51.4 47.1 46.6 57.9    二、中国非制造业采购经理指数运行情况   6月份,非制造业商务活动指数为53.5%,较上月回落1.7个百分点,非制造业扩张力度有所减弱。    分行业看,建筑业商务活动指数为60.1%,与上月持平。服务业商务活动指数为52.3%,低于上月2.0个百分点。从行业情况看,邮政、电信广播电视卫星传输服务、货币金融服务、资本市场服务、保险等行业商务活动指数位于59.0%以上较高景气区间;航空运输、住宿、餐饮、房地产、租赁及商务服务等行业商务活动指数位于临界点以下。     新订单指数为49.6%,比上月下降2.6个百分点,低于临界点,表明非制造业市场需求较上月有所减少。分行业看,建筑业新订单指数为51.2%,比上月回落2.6个百分点;服务业新订单指数为49.4%,比上月下降2.6个百分点。   投入品价格指数为53.4%,比上月回落4.3个百分点,高于临界点,表明非制造业企业用于经营活动的投入品价格涨幅明显收窄。分行业看,建筑业投入品价格指数为51.7%,比上月回落21.9个百分点;服务业投入品价格指数为53.7%,比上月回落1.2个百分点。   销售价格指数为51.4%,低于上月1.4个百分点,高于临界点,表明非制造业销售价格涨幅回落。分行业看,建筑业销售价格指数为52.0%,比上月回落5.0个百分点;服务业销售价格指数为51.2%,比上月回落0.8个百分点。   从业人员指数为48.0%,比上月下降0.9个百分点,表明非制造业用工景气度有所降低。分行业看,建筑业从业人员指数为50.3%,比上月回落2.7个百分点;服务业从业人员指数为47.6%,比上月下降0.6个百分点。   业务活动预期指数为60.8%,比上月回落2.1个百分点,仍位于高位景气区间,表明非制造业企业对行业发展继续看好。分行业看,建筑业业务活动预期指数为63.2%,比上月回落2.5个百分点;服务业业务活动预期指数为60.4%,比上月回落2.0个百分点。 表3 中国非制造业主要分类指数(经季节调整)                                             单位:%    商务活动 新订单 投入品 价格 销售价格 从业人员 业务活动 预期 2020年6月 54.4 52.7 52.9 49.5 48.7 60.3 2020年7月 54.2 51.5 53.0 50.1 48.1 62.2 2020年8月 55.2 52.3 51.9 50.1 48.3 62.1 2020年9月 55.9 54.0 50.6 48.9 49.1 63.0 2020年10月 56.2 53.0 50.9 49.4 49.4 62.9 2020年11月 56.4 52.8 52.7 51.0 48.9 61.2 2020年12月 55.7 51.9 54.3 52.3 48.7 60.6 2021年1月 52.4 48.7 54.5 51.4 47.8 55.1 2021年2月 51.4 48.9 54.7 50.1 48.4 64.0 2021年3月 56.3 55.9 56.2 52.2 49.7 63.7 2021年4月 54.9 51.5 54.9 51.2 48.7 63.0 2021年5月 55.2 52.2 57.7 52.8 48.9 62.9 2021年6月 53.5 49.6 53.4 51.4 48.0 60.8  表4 中国非制造业其他分类指数(经季节调整)                                              单位:%   新出口订单 在手订单 存货 供应商配送时间 2020年6月 43.3 44.8 48.0 52.1 2020年7月 44.5 44.9 48.1 51.9 2020年8月 45.1 44.6 48.5 52.4 2020年9月 49.1 46.3 48.5 52.2 2020年10月 47.0 44.9 48.7 52.3 2020年11月 49.0 45.2 48.8 51.8 2020年12月 47.5 44.7 47.0 51.2 2021年1月 48.0 44.0 47.4 49.8 2021年2月 45.7 44.0 45.9 49.8 2021年3月 50.3 45.9 48.2 51.8 2021年4月 48.1 45.8 47.2 50.9 2021年5月 47.6 44.7 47.2 50.8 2021年6月 45.4 43.8 47.0 51.0    三、中国综合PMI产出指数运行情况   6月份,综合PMI产出指数为52.9%,比上月回落1.3个百分点,表明我国企业生产经营活动总体扩张步伐有所放缓。    附注   1.主要指标解释   采购经理指数(PMI),是通过对企业采购经理的月度调查结果统计汇总、编制而成的指数,它涵盖了企业采购、生产、流通等各个环节,包括制造业和非制造业领域,是国际上通用的监测宏观经济走势的先行性指数之一,具有较强的预测、预警作用。综合PMI产出指数是PMI指标体系中反映当期全行业(制造业和非制造业)产出变化情况的综合指数。PMI高于50%时,反映经济总体较上月扩张;低于50%,则反映经济总体较上月收缩。   2.调查范围   涉及《国民经济行业分类》(GB/T4754-2017)中制造业的31个行业大类,3000家调查样本;非制造业的43个行业大类,4200家调查样本。   3.调查方法   采购经理调查采用PPS(Probability Proportional to Size)抽样方法,以制造业或非制造业行业大类为层,行业样本量按其增加值占全部制造业或非制造业增加值的比重分配,层内样本使用与企业主营业务收入成比例的概率抽取。   本调查由国家统计局直属调查队具体组织实施,利用国家统计联网直报系统对企业采购经理进行月度问卷调查。   4.计算方法   (1)分类指数的计算方法。制造业采购经理调查指标体系包括生产、新订单、新出口订单、在手订单、产成品库存、采购量、进口、主要原材料购进价格、出厂价格、原材料库存、从业人员、供应商配送时间、生产经营活动预期等13个分类指数。非制造业采购经理调查指标体系包括商务活动、新订单、新出口订单、在手订单、存货、投入品价格、销售价格、从业人员、供应商配送时间、业务活动预期等10个分类指数。分类指数采用扩散指数计算方法,即正向回答的企业个数百分比加上回答不变的百分比的一半。由于非制造业没有合成指数,国际上通常用商务活动指数反映非制造业经济发展的总体变化情况。   (2)制造业PMI指数的计算方法。制造业PMI是由5个扩散指数(分类指数)加权计算而成。5个分类指数及其权数是依据其对经济的先行影响程度确定的。具体包括:新订单指数,权数为30%;生产指数,权数为25%;从业人员指数,权数为20%;供应商配送时间指数,权数为15%;原材料库存指数,权数为10%。其中,供应商配送时间指数为逆指数,在合成制造业PMI指数时进行反向运算。   (3)综合PMI产出指数的计算方法。综合PMI产出指数由制造业生产指数与非制造业商务活动指数加权求和而成,权数分别为制造业和非制造业占GDP的比重。   5.季节调整   采购经理调查是一项月度调查,受季节因素影响,数据波动较大。现发布的指数均为季节调整后的数据。 

python 两个栈实现一个队列 && 两个队列实现一个栈

python 两个栈实现一个队列 && 两个队列实现一个栈
你是做IT的料吗?来挑战一下
50+专题演讲、现场代码PK、黑客马拉松,14大沉浸式应用场景……不说了,报名要紧
剑指Offer09.用两个栈实现队列
https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/solution/jian-zhi-offer09yong-liang-ge-zhan-shi-x-hybm/
难度:简单
题目:
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,
分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead操作返回 -1 )
提示:
1 <= values <= 10000
*多会对 appendTail、deleteHead 进行 10000 次调用
示例:
示例 1:
输入:
[“CQueue”,”appendTail”,”deleteHead”,”deleteHead”]
[[],[3],[],[]]
输出:[null,null,3,-1]
示例 2:
输入:
[“CQueue”,”deleteHead”,”appendTail”,”appendTail”,”deleteHead”,”deleteHead”]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]
分析
首先扫盲队列与栈的知识:
队列:先进先出
栈:后进先出
那么,为什么要双栈实现队列呢?其实大家只要思考下:
我们准备两个栈add_stack和pop_stack:
先把1,2,3先挨个加入add_stack栈
下来依次将add_stack栈内的数据出栈,同时将出栈的数据加入pop_stack栈中
1、2执行完成后,add_stack变成了空,pop_stack栈中存储的数据成了[3,2,1]
那么下次出栈时,直接将pop_stack的栈内数据弹出,不就成了列队的先进先出!
关于什么时候执行2步骤需要注意下:
如果pop_stack栈中有数据,就直接return pop的数据
如果pop_stack栈中没有数据
a. add_stack也没有数据,return -1
b. add_stack有数据,执行上面步骤2,将add_stack数据加入pop_stack中
返回pop_stack栈弹出的数据
解题:
class CQueue:
    def __init__(self):
        self.add_stack, self.pop_stack = [], []
    def appendTail(self, value: int) -> None:
        self.add_stack.append(value)
    def deleteHead(self) -> int:
        if self.pop_stack:
            return self.pop_stack.pop()
        if not self.add_stack:
            return -1
        while self.add_stack:
            self.pop_stack.append(self.add_stack.pop())
        return self.pop_stack.pop()
225.用队列实现栈
https://leetcode-cn.com/problems/implement-stack-using-queues/solution/225yong-dui-lie-shi-xian-zhan-by-qingfen-igp1/
难度:简单
题目:
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通队列的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
注意:
你只能使用队列的基本操作 —— 也就是push to back、peek/pop from front、size 和is empty这些操作。
你所使用的语言也许不支持队列。你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列, 只要是标准的队列操作即可。
示例:
输入:
[“MyStack”, “push”, “push”, “top”, “pop”, “empty”]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]
解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False
分析
这道题的关键在于每次入队时,如何保证入队后新入队的元素排在队首,题目要求使用两个队列实现。
首先我们创建两个队列,python操作为from collections import deque
元素入队时,将元素加入q1
判断q2是否存在元素,如果存在元素,则将元素依次出队并加入q1的队尾
交换q1与q2
至于出队、查询top、是否为空,都在q2上操作即可
解题:
from collections import deque
class MyStack:
    def __init__(self):
        “””
        Initialize your data structure here.
        “””
        self.q1 = deque()
        self.q2 = deque()
    def push(self, x: int) -> None:
        “””
        Push element x onto stack.
        “””
        self.q1.append(x)
        while self.q2:
            self.q1.append(self.q2.popleft())
        self.q1, self.q2 = self.q2, self.q1
    def pop(self) -> int:
        “””
        Removes the element on top of the stack and returns that element.
        “””
        return self.q2.popleft()
    def top(self) -> int:
        “””
        Get the top element.
        “””
        return self.q2[0]
    def empty(self) -> bool:
        “””
        Returns whether the stack is empty.
        “””
        return not self.q2

5781.删除一个字符串中所有出现的给定子字符串 有趣的三种解法!

5781.删除一个字符串中所有出现的给定子字符串 有趣的三种解法!
免费获得鹅厂游戏运研《白皮书》,还有机会获得精美*品!
揭秘鹅厂游戏运维研发的架构部署与技术实践之道,限时免费获取
5781.删除一个字符串中所有出现的给定子字符串
https://leetcode-cn.com/problems/remove-all-occurrences-of-a-substring/solution/5781shan-chu-yi-ge-zi-fu-chuan-zhong-suo-agj2/
难度:中等
题目
给你两个字符串 s 和 part ,请你对 s 反复执行以下操作直到 所有 子字符串 part 都被删除:
找到 s 中 *左边 的子字符串 part ,并将它从 s 中删除。 请你返回从 s 中删除所有 part 子字符串以后得到的剩余字符串。
一个 子字符串 是一个字符串中连续的字符序列。
提示:
1 <= s.length <= 1000
1 <= part.length <= 1000
s 和 part 只包小写英文字母。
示例
示例 1:
输入:s = “daabcbaabcbc”, part = “abc”
输出:”dab”
解释:以下操作按顺序执行:
– s = “daabcbaabcbc” ,删除下标从 2 开始的 “abc” ,得到 s = “dabaabcbc” 。
– s = “dabaabcbc” ,删除下标从 4 开始的 “abc” ,得到 s = “dababc” 。
– s = “dababc” ,删除下标从 3 开始的 “abc” ,得到 s = “dab” 。
此时 s 中不再含有子字符串 “abc” 。
示例 2:
输入:s = “axxxxyyyyb”, part = “xy”
输出:”ab”
解释:以下操作按顺序执行:
– s = “axxxxyyyyb” ,删除下标从 4 开始的 “xy” ,得到 s = “axxxyyyb” 。
– s = “axxxyyyb” ,删除下标从 3 开始的 “xy” ,得到 s = “axxyyb” 。
– s = “axxyyb” ,删除下标从 2 开始的 “xy” ,得到 s = “axyb” 。
– s = “axyb” ,删除下标从 1 开始的 “xy” ,得到 s = “ab” 。
此时 s 中不再含有子字符串 “xy” 。
分析
作为中等题,这道不是很难,做过类似的括号匹配等同类型题目,立刻就能想到通过栈来处理。
栈操作
循环s进行入栈操作
当栈内元素>= len(part) 并且相等时,将数据弹出
*终将栈内数据”.join(stack)返回
字符串模拟
由于这道题两个入参都是字符串,所以我们通过字符串来模拟栈操作
无赖的省事儿解法
这里分享一个虽然很无赖的解法,但真的很欢乐,我们通过无线replace替换来完成这道题。
这么写真的好赖皮,就是不知道面试的时候,会不会被打,哈哈。
解题
栈解题
class Solution:
    def removeOccurrences(self, s: str, part: str) -> str:
        stack, part, ln = [], list(part), len(part)
        for i in s:
            stack.append(i)
            if len(stack) >= len(part):
                while stack[len(stack) – len(part):] == part:
                    for _ in range(len(part)):
                        stack.pop()
        return ”.join(stack)
字符串模拟
class Solution:
    def removeOccurrences(self, s: str, part: str) -> str:
        ret = ”
        ln = len(part)
        for i in s:
            ret += i
            while ret.endswith(part):
                ret = ret[:len(ret) – ln]
        return ret
replace替换
class Solution:
    def removeOccurrences(self, s: str, part: str) -> str:
        while part in s:
            s = s.replace(part,”)
        return s

K个数、K个点、K个元素,3K堆排序,类比三解题!

K个数、K个点、K个元素,3K堆排序,类比三解题!
你是做IT的料吗?来挑战一下
50+专题演讲、现场代码PK、黑客马拉松,14大沉浸式应用场景……不说了,报名要紧
面试题17.14.*小K个数
https://leetcode-cn.com/problems/smallest-k-lcci/solution/mian-shi-ti-1714zui-xiao-kge-shu-ji-chu-k9jd8/
难度:中等
题目:
设计一个算法,找出数组中*小的k个数。以任意顺序返回这k个数均可。
提示:
0 <= len(arr) <= 100000
0 <= k <= min(100000, len(arr))
示例:
输入: arr = [1,3,5,7,2,4,6,8], k = 4
输出: [1,2,3,4]
分析
这道题之所以定义为堆排序类型题,就是因为可以任意顺序返回。 这里推排序有两种思路:
小根堆:每次获取的数据都无脑入堆,然后*终将前K个数字返回
大根堆:仅维护K个长度的堆,由于python没有,需要入赋值,如果当前的数大于heap[0],则堆顶出堆,当前数入堆,*终返回。
至于 return sorted(arr)[:k] 的写法,面试时候不怕被打,你就这么写。
解题:
import heapq
class Solution:
    def smallestK(self, arr, k):
        if k == 0:
            return []
        hq = []
        for i in arr:
            if len(hq) < k:
                heapq.heappush(hq, -i)
            else:
                if hq[0] < -i:
                    heapq.heappop(hq)
                    heapq.heappush(hq, -i)
        return [-i for i in hq]
347.前K个高频元素
https://leetcode-cn.com/problems/top-k-frequent-elements/solution/347qian-kge-gao-pin-yuan-su-nei-zhi-han-zlfi7/
难度:中等
题目:
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
提示:
1 <= nums.length <= 10 ^ 5
k 的取值范围是 [1, 数组中不相同的元素的个数]
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的
示例:
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
分析
遇到重复数字求频率,首先要想到Counter计数原则,将当前的数组转换为hash表 num:frequency 的类型然后再做操作。
即: dic = collections.Counter(nums)
分享两种方法(虽然面试官更希望你写第二种,但如果同时写出*种,能展示出你对基础模块的掌握度):
1.sorted方法
使用sorted自带的排序和列表切片后返回,可以压缩到一行代码
2.堆排序
这道题提及了任意顺序返回均可,那就可以通过使用堆的方法来解决了。这道题使用小根堆很方便。
类似的题目有:
面试题17.14.*小K个数
sorted解题:
from collections import Counter
class Solution:
    def topKFrequent(self, nums, k):
        return [x[0] for x in sorted(Counter(nums).items(),key = lambda x: x[1],reverse=True)[:k]]
堆排序解题:
from collections import Counter
import heapq
class Solution:
    def topKFrequent(self, nums, k):
        dic = Counter(nums)
        hp = []
        for num, req in dic.items():
            if len(hp) < k:
                heapq.heappush(hp, (req, num))
            else:
                if req > hp[0][0]:
                    heapq.heappop(hp)
                    heapq.heappush(hp, (req, num))
        return [x[1] for x in hp]
973.*接近原点的K个点
https://leetcode-cn.com/problems/k-closest-points-to-origin/solution/973zui-jie-jin-yuan-dian-de-kge-dian-pyt-4jro/
难度:中等
题目:
我们有一个由平面上的点组成的列表 points。需要从中找出 K 个距离原点 (0, 0) *近的点。
(这里,平面上两点之间的距离是欧几里德距离。)
你可以按任何顺序返回答案。除了点坐标的顺序之外,答案确保是唯一的。
提示:
1 <= K <= points.length <= 10000
-10000 < points[i][0] < 10000
-10000 < points[i][1] < 10000
示例:
示例 1:
输入:points = [[1,3],[-2,2]], K = 1
输出:[[-2,2]]
解释:
(1, 3) 和原点之间的距离为 sqrt(10),
(-2, 2) 和原点之间的距离为 sqrt(8),
由于 sqrt(8) < sqrt(10),(-2, 2) 离原点更近。
我们只需要距离原点*近的 K = 1 个点,所以答案就是 [[-2,2]]。
示例 2:
输入:points = [[3,3],[5,-1],[-2,4]], K = 2
输出:[[3,3],[-2,4]]
(答案 [[-2,4],[3,3]] 也会被接受。)
分析
遇到求前K的题目,内置的sorted和堆排序无脑安排上就对了,类似的题目有:
面试题17.14.*小K个数
347.前K个高频元素
这道题同样的,我们使用堆排序(大根堆)来完成解题,维护一个K大的堆,然后每次判断距离是否比堆顶的数字大。
如果比堆顶数字大,弹出堆顶,将当前距离及点信息以列表方式入堆即可。
解题:
import heapq
class Solution:
    def kClosest(self, points, k):
        hp = []
        for point in points:
            distance = sum(map(lambda x: abs(x ** 2), point))
            if len(hp) < k:
                heapq.heappush(hp, [-distance, point])
            else:
                if -distance > hp[0][0]:
                    heapq.heappop(hp)
                    heapq.heappush(hp, [-distance, point])
        return [point for distance, point in hp]
友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速