原理
  这种效果是通过自定义控件的方式来实现的,我自定义了一个控件类型,这个自定义控件(PullDownDumperLayout)继承自线性布局(LinearLayout)。
  用户可以下拉弹出的那个视图,例如微信的小程序列表,开发者只是将这个视图移出了父元素之外,所以不可见,我们暂且称之为隐藏头部,只有下拉到一定程度才会弹出,而主体,例如微信的联系人列表,则是可见的,布局见下图。

实现这个效果需要我们做三件工作:

隐藏作为头部的控件
监听用户对屏幕的操作事件
实现下拉回弹的动画效果
  我们这个自定义控件会自动获取内部第一个子元素充当头部,其余的元素则是充当可见的主体(详见代码中的注释)。
  基本的布局原理差不多就这样了,但是我们还需要让自定义控件监听用户的手势操作,例如上下滑动等。这里我和灵感来源的那篇博客一样,让自定义控件实现View.OnTouchListener接口,实现内部的onTouch方法可以监听来自屏幕的所有触摸操作。代码中我让头部和第二个子元素(可见的主体)注册了这个监听器,这是为了方便读者理解,读者可根据自己的需求进行修改。
  注意,对于不能监听屏幕触摸事件的控件需要添加:
    android:clickable="true"
  至此,我们已经可以进行布局和监听用户手势了,但是还需要实现一个头部展开和隐藏的动画效果。当用户将隐藏头部下拉或上滑到一定高度时,这个效果就会被触发,这需要依赖上面所述的onTouch方法。动画效果的实现需要另开一个线程进行操作,线程的启动方式我们可以采用继承AsyncTask类来实现。
  除此之外,我们可能会多次复用这个控件,所以在自定义控件类的最后还需要一些调整参数的set方法。
  这里提个醒,在接下来的代码中,我们的自定义控件因为继承自LinearLayout,里面需要重写onLayout方法,而onLayout方法顾名思义就是布局,这个方法在Activity中的onCreate方法执行之后才会被调用,所以我们可以在Activity的onCreate方法中利用findViewById获取实例,调用上面提到的set方法进行参数的初始化。
  LinearLayout中不止onLayout一个方法,详细解析请读者移步其他关于XML标签加载过程的文章,这里不做赘述。

代码
PullDownDumperLayout .java:

public class PullDownDumperLayout extends LinearLayout implements View.OnTouchListener {

/**
* 取布局中的第一个子元素为下拉隐藏头部
*/
private View mHeadLayout;

/**
* 隐藏头部布局的高的负值
*/
private int mHeadLayoutHeight;

/**
* 隐藏头部的布局参数
*/
private MarginLayoutParams mHeadLayoutParams;

/**
* 判断是否为第一次初始化,第一次初始化需要把headView移出界面外
*/
private boolean mOnLayoutIsInit=false;

/**
* 移动时,前一个坐标
*/
private float mMoveY;

/**
* 如果为false,会退出头部展开或隐藏动画
*/
private boolean mChangeHeadLayoutTopMargin;

/**
* 触发动画的分界线,由mRatio计算得到
*/
private int mBoundary;

/**
* 头部布局的隐藏和展开速度,以及单次执行时间
*/
private int mHeadLayoutHideSpeed;
private int mHeadLayoutUnfoldSpeed;
private long mSleepTime;

/**
* 触发动画的分界线,头部布局上半部分和整体高度的比例
*/
private double mRatio;

public PullDownDumperLayout(Context context, AttributeSet attrs) {
super(context, attrs);
//初始化参数,根据自己的需求调整
mHeadLayoutHideSpeed=-20;
mHeadLayoutUnfoldSpeed=20;
mSleepTime=10;
mRatio=0.5;
}

/**
* 布局开始设置每一个控件
* 在activity的onCreate执行之后才会执行
* 因此可以在onCreate中调用set方法设置参数
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if(!mOnLayoutIsInit && changed) {
//将第一个子元素作为头部移出界面外
mHeadLayout = this.getChildAt(0);
mHeadLayoutHeight=-mHeadLayout.getHeight();
mBoundary=(int)(mRatio*mHeadLayoutHeight);//计算触发动画分界线
mHeadLayoutParams=(MarginLayoutParams) mHeadLayout.getLayoutParams();
mHeadLayoutParams.topMargin=mHeadLayoutHeight;
mHeadLayout.setLayoutParams(mHeadLayoutParams);
//TODO 设置手势监听器,不能触碰的控件需要添加android:clickable="true"
getChildAt(1).setOnTouchListener(this);
mHeadLayout.setOnTouchListener(this);
//标记已被初始化
mOnLayoutIsInit=true;
}
}

/**
* 屏幕触摸操作监听器
* @return false则注册本监听器的控件将不会对事件做出响应,true则相反
*/
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mMoveY=event.getRawY();//捕获按下时的坐标,初始化mMoveY
mChangeHeadLayoutTopMargin=false;
break;
case MotionEvent.ACTION_MOVE:
float currY=event.getRawY();
int vector=(int)(currY-mMoveY);//向量,用于判断手势的上滑和下滑
mMoveY=currY;
//判断是否为滑动
if(Math.abs(vector)==0){
return false;
}
//头部完全隐藏时不再向上滑动
if (vector < 0 && mHeadLayoutParams.topMargin <= mHeadLayoutHeight) {
return false;
}
//头部完全展开时不再向下滑动
if (vector > 0 && mHeadLayoutParams.topMargin >= 0) {
return false;
}

//对增量进行修正,对滑动距离进行减半
int topMargin = mHeadLayoutParams.topMargin + (vector/2);//阻尼值
if(topMargin>0){
// 瞬间拉动的距离超过了头部高度,因为这一瞬间很短,这里采用直接赋值的方式
// 如需平滑过渡,要另开线程,并且监听到ACTION_DOWN时线程可被打断
topMargin = 0;
}
else if(topMargin<mHeadLayoutHeight){
// 瞬间拉动的距离超过了头部高度,因为这一瞬间很短,这里采用直接赋值的方式
// 如需平滑过渡,要另开线程,并且监听ACTION_DOWN时线程可被打断
topMargin = mHeadLayoutHeight;
}
//用户对屏幕的滑动将会改变控件的TopMargin
mHeadLayoutParams.topMargin = topMargin ;
mHeadLayout.setLayoutParams(mHeadLayoutParams);
break;
default:
//TODO 出现其他触碰事件,如MotionEvent.ACTION_UP时,根据阈值判断此时头部应该弹出还是隐藏
mChangeHeadLayoutTopMargin=true;
if(mHeadLayoutParams.topMargin<=mBoundary){
//隐藏
new MoveHeaderTask().execute(true);
}
else{
//展开
new MoveHeaderTask().execute(false);
}
break;
}
return false;
}

/**
* 新线程,隐藏或者展开头部布局,线程可被ACTION_DOWN打断
*/
class MoveHeaderTask extends AsyncTask<Boolean, Integer, Integer> {

/**
*
* @param opt true为隐藏动画,false为展开动画
* @return
*/
@Override
protected Integer doInBackground(Boolean... opt) {
int topMargin=mHeadLayoutParams.topMargin;
//true为隐藏,false为展开
int speed=(opt[0])?mHeadLayoutHideSpeed:mHeadLayoutUnfoldSpeed;
while(mChangeHeadLayoutTopMargin){
topMargin += speed;
if (topMargin <= mHeadLayoutHeight||topMargin>=0) {
topMargin=(opt[0])?mHeadLayoutHeight:0;
publishProgress(topMargin);
break;
}
publishProgress(topMargin);
sleep(mSleepTime);
}
return null;
}

//调用publishProgress后会执行
@Override
protected void onProgressUpdate(Integer... topMargin) {
mHeadLayoutParams.topMargin=topMargin[0];
mHeadLayout.setLayoutParams(mHeadLayoutParams);
}

}

//调整参数
public void setHeadLayoutHideSpeed(int speed){
this.mHeadLayoutHideSpeed=speed;
}
public void setHeadLayoutUnfoldSpeed(int speed){
this.mHeadLayoutUnfoldSpeed=speed;
}
public void setSleepTime(long time){
this.mSleepTime=time;
}
public void setRatio(double ratio){
this.mRatio=ratio;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
tools:context=".MainActivity">

<com.example.pulldowndumpertest.PullDownDumperLayout
android:tag="记得将这个标签修改为自己的包名"
android:id="@+id/PullDownDumper"
android:layout_width="900px"
android:layout_height="1920px"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:background="@null"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="500px"
android:orientation="vertical"
android:background="@color/colorPrimary"
android:clickable="true">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="隐藏头部"
android:textSize="100px"
android:gravity="center"
android:textColor="#FFFFFF"
android:background="@null"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="1700px"
android:background="@color/colorPrimaryDark"
android:clickable="true">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="可见主体"
android:textSize="100px"
android:gravity="center"
android:textColor="#FFFFFF"
android:background="@null"/>
</LinearLayout>
</com.example.pulldowndumpertest.PullDownDumperLayout>

</android.support.constraint.ConstraintLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
MainActivity.java:

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

//TODO 读者可在这里初始化参数
PullDownDumperLayout pddl=findViewById(R.id.PullDownDumper);

}
}
1
2
3
4
5
6
7
8
9
10
11
下面是笔者正在使用的自定义控件,比上述的控件多了一个效果:
  头部处于隐藏或展开的不同状态时,触发动画效果的分界线可以随状态不同而改变。
  还是拿最新版的微信小程序入口来讲,用户在下拉时,小程序界面会占用整个屏幕,如果触发动画的分界线太低,这样导致的结果是用户可能无法通过上滑重新返回联系人列表,但由于微信没有对滑动距离进行减半处理,所以不存在上述问题,可能是出于防止误触的原因,从小程序界面返回联系人列表的方式改用点击底部的一个按钮。而我的控件可以通过改变触发动画效果的分界线来解决这一问题,感兴趣的读者可以研究一下。

public class PullDownDumperLayout extends LinearLayout implements View.OnTouchListener {

/**
* 取布局中的第一个子元素为下拉隐藏头部
*/
private View mHeadLayout;

/**
* 隐藏头部布局的高的负值
*/
private int mHeadLayoutHeight;

/**
* 隐藏头部的布局参数
*/
private MarginLayoutParams mHeadLayoutParams;

/**
* 判断是否为第一次初始化,第一次初始化需要把headView移出界面外
*/
private boolean mOnLayoutIsInit=false;

/**
* 从配置获取的滚动判断阈值,为两点间的距离,超过此阈值判断为滚动
*/
// private int mScaledTouchSlop;

/**
* 按下时的y轴坐标
*/
// private float mDownY;

/**
* 移动时,前一个坐标
*/
private float mMoveY;

/**
* 如果为false,会退出头部展开或隐藏动画
*/
private boolean mChangeHeadLayoutTopMargin;

/**
* 头部布局的隐藏和展开速度,以及单次执行时间
*/
private int mHeadLayoutHideSpeed;
private int mHeadLayoutUnfoldSpeed;
private long mSleepTime;

/**
* 初始化头部布局的偏移值,数值越大,头部可见部分越多,预设值为0,即初始时头部完全不可见
*/
private int mTopMarginOffset;

/**
* 触发动画的分界线,头部布局上半部分和整体高度的比例
*/
private double mUnfoldRatio;
private double mHideRatio;

/**
* 触发动画的分界线,初始值由mRatio计算得到
* 头部处于隐藏时等于mUnfoldBoundary
* 头部处于展开时等于mHideBoundary
* mBoundary在onTouch的ACTION_DOWN中变化
*/
private int mBoundary;
private int mUnfoldBoundary;
private int mHideBoundary;

/**
* 阻尼值,越大越难拖动,呈线性趋势
*/
private int mDumper;

public PullDownDumperLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// mScaledTouchSlop= ViewConfiguration.get(context).getScaledTouchSlop();
mHeadLayoutHideSpeed=-30;
mHeadLayoutUnfoldSpeed=30;
mSleepTime=10;
mUnfoldRatio=0.6;
mHideRatio=mUnfoldRatio;
mDumper=2;
mTopMarginOffset=-200;
}

/**
* 布局开始设置每一个控件
* 在activity的onCreate执行之后才会执行
* 因此可以在onCreate中调用set方法设置参数
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
//只初始化一次
if(!mOnLayoutIsInit && changed) {
//将第一个子元素作为头部移出界面外
mHeadLayout = this.getChildAt(0);
mHeadLayoutHeight=-mHeadLayout.getHeight();
mUnfoldBoundary=(int)(mUnfoldRatio*mHeadLayoutHeight);//计算触发展开动画分界线
mHideBoundary=(int)(mHideRatio*mHeadLayoutHeight);//计算触发隐藏动画分界线
mBoundary=mUnfoldBoundary;//触发动画的分界线初始为mUnfoldBoundary
mHeadLayoutHeight-=mTopMarginOffset;//头部隐藏布局可见的部分
mHeadLayoutParams=(MarginLayoutParams) mHeadLayout.getLayoutParams();
mHeadLayoutParams.topMargin=mHeadLayoutHeight;
mHeadLayout.setLayoutParams(mHeadLayoutParams);
//TODO 设置手势监听器,不能触碰的控件需要添加android:clickable="true"
getChildAt(1).setOnTouchListener(this);
mHeadLayout.setOnTouchListener(this);
//标记已被初始化
mOnLayoutIsInit=true;
}
}

/**
* 屏幕触摸操作监听器
* @return false: 注册本监听器的控件将不会对事件做出响应,true则相反
*/
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//根据此时处于完全展开或完全隐藏决定mBoundary的值,如果两种情况都不满足则不做改变
if(mHeadLayoutParams.topMargin==mHeadLayoutHeight)
mBoundary=mUnfoldBoundary;
else if(mHeadLayoutParams.topMargin==0)
mBoundary=mHideBoundary;

// mDownY=event.getRawY();//获取按下的屏幕y坐标
mMoveY=event.getRawY();
mChangeHeadLayoutTopMargin=false;//false会打断隐藏或展开头部布局的动画
break;
case MotionEvent.ACTION_MOVE:
float currY=event.getRawY();
int vector=(int)(currY-mMoveY);//向量,用于判断手势的上滑和下滑
mMoveY=currY;
//判断是否为滑动
if(Math.abs(vector)==0){
return false;
}
//头部完全隐藏时不再向上滑动
if (vector < 0 && mHeadLayoutParams.topMargin <= mHeadLayoutHeight) {
return false;
}
//头部完全展开时不再向下滑动
else if (vector > 0 && mHeadLayoutParams.topMargin >= 0) {
return false;
}

//对增量进行修正
int topMargin = mHeadLayoutParams.topMargin + (vector/mDumper);
if(topMargin>0){
// 瞬间拉动的距离超过了头部高度,因为这一瞬间很短,这里采用直接赋值的方式
// 如需实现平滑过渡,要另开线程,并且监听到ACTION_DOWN时线程可被打断
topMargin = 0;
}
else if(topMargin<mHeadLayoutHeight){
// 瞬间拉动的距离超过了头部高度,因为这一瞬间很短,这里采用直接赋值的方式
// 如需实现平滑过渡,要另开线程,并且监听ACTION_DOWN时线程可被打断
topMargin = mHeadLayoutHeight;
}

//使参数生效
mHeadLayoutParams.topMargin = topMargin ;
mHeadLayout.setLayoutParams(mHeadLayoutParams);
break;
default:
//出现其他触碰事件,如MotionEvent.ACTION_UP时,根据阈值mBoundary判断此时头部应该弹出还是隐藏
mChangeHeadLayoutTopMargin=true;//允许执行动画
if(mHeadLayoutParams.topMargin<=mBoundary){
//隐藏
new MoveHeaderTask().execute(true);
}
else{
//展开
new MoveHeaderTask().execute(false);
}
break;
}
return false;
}

/**
* 新线程,隐藏或者展开头部布局,线程可被ACTION_DOWN打断
*/
private class MoveHeaderTask extends AsyncTask<Boolean, Integer, Integer> {

/**
*
* @param opt true为隐藏动画,false为展开动画
* @return
*/
@Override
protected Integer doInBackground(Boolean... opt) {
int topMargin=mHeadLayoutParams.topMargin;
//true为隐藏,false为展开
int speed=(opt[0])?mHeadLayoutHideSpeed:mHeadLayoutUnfoldSpeed;
while(mChangeHeadLayoutTopMargin){
topMargin += speed;
if (topMargin <= mHeadLayoutHeight||topMargin>=0) {
topMargin=(opt[0])?mHeadLayoutHeight:0;
publishProgress(topMargin);
break;
}
publishProgress(topMargin);
sleep(mSleepTime);
}
return null;
}

//调用publishProgress后会执行
@Override
protected void onProgressUpdate(Integer... topMargin) {
mHeadLayoutParams.topMargin=topMargin[0];
mHeadLayout.setLayoutParams(mHeadLayoutParams);
}

}

//调整参数
public void setHeadLayoutHideSpeed(int speed){
this.mHeadLayoutHideSpeed=speed;
}
public void setHeadLayoutUnfoldSpeed(int speed){
this.mHeadLayoutUnfoldSpeed=speed;
}
public void setSleepTime(long time){
this.mSleepTime=time;
}
public void setDumper(int dumper){
this.mDumper=dumper;
}
public void setTopMarginOffset(int offset){
this.mTopMarginOffset=-offset;
}

/**
* 头部处于隐藏状态时,触发展开动画的分界线
* @param ratio 头部布局上部分与下部分的分界线
*/
public void setUnfoldRatio(double ratio){
this.mUnfoldRatio=ratio;
}

/**
* 头部处于展开状态时,触发隐藏动画的分界线
* @param ratio 头部布局上部分与下部分的分界线
*/
public void setHideRatio(double ratio){
this.mHideRatio=ratio;
}
}```

---------------------

Android安卓下拉阻尼效果实现原理及简单实例的更多相关文章

  1. Android PullToRefresh下拉刷新控件的简单使用

    PullToRefresh这个开源库早就听说了,不过一直没用过.作为一个经典的的开源库,我觉得还是有必要认识一下. 打开github上的网址:https://github.com/chrisbanes ...

  2. Android仿苹果版QQ下拉刷新实现(一) ——打造简单平滑的通用下拉刷新控件

    前言: 忙完了结婚乐APP的开发,终于可以花一定的时间放在博客上了.好了,废话不多说,今天我们要带来的效果是苹果版本的QQ下拉刷新.首先看一下目标效果以及demo效果:      因为此效果实现的步骤 ...

  3. Xamarin. Android实现下拉刷新功能

    PS:发现文章被其他网站或者博客抓取后发表为原创了,给图片加了个水印 下拉刷新功能在安卓和iOS中非常常见,一般实现这样的功能都是直接使用第三方的库,网上能找到很多这样的开源库.然而在Xamarin. ...

  4. Android实现下拉导航选择菜单效果

    本文介绍在Android中如何实现下拉导航选择菜单效果.   关于下拉导航选择菜单效果在新闻客户端中用的比较多,当然也可以用在其他的项目中,这样可以很方便的选择更多的菜单.我们可以让我们的应用顶部有左 ...

  5. Android智能下拉刷新加载框架—看这些就够了

    一些值得学习的几个下拉刷新上拉加载开源库 Android智能下拉刷新框架-SmartRefreshLayout 支持所有的 View(AbsListView.RecyclerView.WebView. ...

  6. html5实现移动端下拉刷新(原理和代码)

    这篇文章给大家介绍的内容是关于html5实现移动端下拉刷新(原理和代码),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 移动端的下拉刷新是一个很常见的功能,也有许多开源库实现了这个功 ...

  7. android一个下拉放大库bug的解决过程及思考

    android一个下拉放大库bug的解决过程及思考 起因 项目中要做一个下拉缩放图片的效果,搜索了下github上面,找到了两个方案. https://github.com/Frank-Zhu/Pul ...

  8. Win Socket编程原理及简单实例

    [转]http://www.cnblogs.com/tornadomeet/archive/2012/04/11/2442140.html 使用Linux Socket做了小型的分布式,如Linux ...

  9. android官方下拉刷新控件SwipeRefreshLayout的使用

    可能开发安卓的人大多数都用过很多下拉刷新的开源组件,但是今天用了官方v4支持包的SwipeRefreshLayout觉得效果也蛮不错的,特拿出来分享. 简介:SwipeRefreshLayout组件只 ...

随机推荐

  1. JAVA Synchronized (二)

    一,介绍 本文介绍JAVA多线程中的synchronized关键字作为对象锁的一些知识点. 所谓对象锁,就是就是synchronized 给某个对象 加锁.关于 对象锁 可参考:这篇文章 二,分析 s ...

  2. apple-touch-startup-image 制作iphone web应用程序的启动画面

    为ipad制作web应用程序的启动画面时发现个问题,只能显示竖屏图,横屏图出不来,如下: 首先页面头部里要加入(这个是APP启动画面图片,如果不设置,启动画面就是白屏,图片像素就是手机全屏的像素) & ...

  3. View Programming Guide for iOS ---- iOS 视图编程指南(五)---Animations

      Animations Animations provide fluid visual transitions between different states of your user inter ...

  4. docker容器管理基础

    1.命令: docker info #查看服务器上docker详细信息 docker search #搜索镜像 docker image pull nginx:1.14-alpine #下载一个镜像 ...

  5. sql server2008配置管理工具服务显示远程过程调用失败

    SQL SERVER2008配置管理工具服务显示远程过程调用失败   前两天,装了VS2012后,打开SQL2008配置管理工具,发现SQL服务名称里什么也没有,只有一个提示:(如图) 上网搜了,试了 ...

  6. POJ1050【DP】

    题意: 求一个最大子矩阵和. 思路: 枚举行区间,然后求一个最大子序列和. 贴一发挫code- #include <iostream> #include <cstdio> #i ...

  7. python help(int)

    class int(object) | int(x=0) -> integer | int(x, base=10) -> integer | | Convert a number or s ...

  8. springcloud(一) 服务拆分

    一般我们的项目如果需要从单应用服务升级到微服务,必须要将原来的服务做拆分,我这边的拆分也是基于将之前spb-demo的springboot单应用做拆分,拆分出三个应用,spb-brian-query- ...

  9. P1228-重叠的图像

    一道很水的topsort,唉?怎么交了14遍...(某人用我的代码刚好卡过,我怎么过不去...[鄙视][鄙视][鄙视]) #include <bits/stdc++.h> using na ...

  10. Mike and gcd problem CodeForces - 798C

    题目 (智商题 or 糟心的贪心) 题意: 有一个数列a1,a2,...,an,每次操作可以将相邻的两个数x,y变为x-y,x+y,求最少的操作数使得gcd(a1,a2,...,an)>1.gc ...