RxJava去除抖动,防止ios开发 按钮重复点击击的操作符,有哪些

RxJava去除抖动,防止按钮重复点击的操作符,有哪些_百度知道
RxJava去除抖动,防止按钮重复点击的操作符,有哪些
我有更好的答案
ReactiveX的每种编程语言的实现都实现了一组操作符的集合。不同的实现之间有很多重叠的部分,也有一些操作符只存在特定的实现中。每种实现都倾向于用那种编程语言中他们熟悉的上下文中相似的方法给这些操作符命名。本文首先会给出ReactiveX的核心操作符列表和对应的文档链接,后面还有一个决策树用于帮助你根据具体的场景选择合适的操作符。最后有一个语言特定实现的按字母排序的操作符列表。如果你想实现你自己的操作符,可以参考这里:实现自定义操作符创建操作用于创建Observable的操作符Create — 通过调用观察者的方法从头创建一个ObservableDefer — 在观察者订阅之前不创建这个Observable,为每一个观察者创建一个新的ObservableEmpty/Never/Throw — 创建行为受限的特殊ObservableFrom — 将其它的对象或数据结构转换为ObservableInterval — 创建一个定时发射整数序列的ObservableJust — 将对象或者对象集合转换为一个会发射这些对象的ObservableRange — 创建发射指定范围的整数序列的ObservableRepeat — 创建重复发射特定的数据或数据序列的ObservableStart — 创建发射一个函数的返回值的ObservableTimer — 创建在一个指定的延迟之后发射单个数据的Observable变换操作这些操作符可用于对Observable发射的数据进行变换,详细解释可以看每个操作符的文档Buffer — 缓存,可以简单的理解为缓存,它定期从Observable收集数据到一个集合,然后把这些数据集合打包发射,而不是一次发射一个FlatMap — 扁平映射,将Observable发射的数据变换为Observables集合,然后将这些Observable发射的数据平坦化的放进一个单独的Observable,可以认为是一个将嵌套的数据结构展开的过程。GroupBy — 分组,将原来的Observable分拆为Observable集合,将原始Observable发射的数据按Key分组,每一个Observable发射一组不同的数据Map — 映射,通过对序列的每一项都应用一个函数变换Observable发射的数据,实质是对序列中的每一项执行一个函数,函数的参数就是这个数据项Scan — 扫描,对Observable发射的每一项数据应用一个函数,然后按顺序依次发射这些值Window — 窗口,定期将来自Observable的数据分拆成一些Observable窗口,然后发射这些窗口,而不是每次发射一项。类似于Buffer,但Buffer发射的是数据,Window发射的是Observable,每一个Observable发射原始Observable的数据的一个子集过滤操作这些操作符用于从Observable发射的数据中进行选择Debounce — 只有在空闲了一段时间后才发射数据,通俗的说,就是如果一段时间没有操作,就执行一次操作Distinct — 去重,过滤掉重复数据项ElementAt — 取值,取特定位置的数据项Filter — 过滤,过滤掉没有通过谓词测试的数据项,只发射通过测试的First — 首项,只发射满足条件的第一条数据IgnoreElements — 忽略所有的数据,只保留终止通知(onError或onCompleted)Last — 末项,只发射最后一条数据Sample — 取样,定期发射最新的数据,等于是数据抽样,有的实现里叫ThrottleFirstSkip — 跳过前面的若干项数据SkipLast — 跳过后面的若干项数据Take — 只保留前面的若干项数据TakeLast — 只保留后面的若干项数据组合操作组合操作符用于将多个Observable组合成一个单一的ObservableAnd/Then/When — 通过模式(And条件)和计划(Then次序)组合两个或多个Observable发射的数据集CombineLatest — 当两个Observables中的任何一个发射了一个数据时,通过一个指定的函数组合每个Observable发射的最新数据(一共两个数据),然后发射这个函数的结果Join — 无论何时,如果一个Observable发射了一个数据项,只要在另一个Observable发射的数据项定义的时间窗口内,就将两个Observable发射的数据合并发射Merge — 将两个Observable发射的数据组合并成一个StartWith — 在发射原来的Observable的数据序列之前,先发射一个指定的数据序列或数据项Switch — 将一个发射Observable序列的Observable转换为这样一个Observable:它逐个发射那些Observable最近发射的数据Zip — 打包,使用一个指定的函数将多个Observable发射的数据组合在一起,然后将这个函数的结果作为单项数据发射错误处理这些操作符用于从错误通知中恢复Catch — 捕获,继续序列操作,将错误替换为正常的数据,从onError通知中恢复Retry — 重试,如果Observable发射了一个错误通知,重新订阅它,期待它正常终止辅助操作一组用于处理Observable的操作符Delay — 延迟一段时间发射结果数据Do — 注册一个动作占用一些Observable的生命周期事件,相当于Mock某个操作Materialize/Dematerialize — 将发射的数据和通知都当做数据发射,或者反过来ObserveOn — 指定观察者观察Observable的调度程序(工作线程)Serialize — 强制Observable按次序发射数据并且功能是有效的Subscribe — 收到Observable发射的数据和通知后执行的操作SubscribeOn — 指定Observable应该在哪个调度程序上执行TimeInterval — 将一个Observable转换为发射两个数据之间所耗费时间的ObservableTimeout — 添加超时机制,如果过了指定的一段时间没有发射数据,就发射一个错误通知Timestamp — 给Observable发射的每个数据项添加一个时间戳Using — 创建一个只在Observable的生命周期内存在的一次性资源条件和布尔操作这些操作符可用于单个或多个数据项,也可用于ObservableAll — 判断Observable发射的所有的数据项是否都满足某个条件Amb — 给定多个Observable,只让第一个发射数据的Observable发射全部数据Contains — 判断Observable是否会发射一个指定的数据项DefaultIfEmpty — 发射来自原始Observable的数据,如果原始Observable没有发射数据,就发射一个默认数据SequenceEqual — 判断两个Observable是否按相同的数据序列SkipUntil — 丢弃原始Observable发射的数据,直到第二个Observable发射了一个数据,然后发射原始Observable的剩余数据SkipWhile — 丢弃原始Observable发射的数据,直到一个特定的条件为假,然后发射原始Observable剩余的数据TakeUntil — 发射来自原始Observable的数据,直到第二个Observable发射了一个数据或一个通知TakeWhile — 发射原始Observable的数据,直到一个特定的条件为真,然后跳过剩余的数据算术和聚合操作这些操作符可用于整个数据序列Average — 计算Observable发射的数据序列的平均值,然后发射这个结果Concat — 不交错的连接多个Observable的数据Count — 计算Observable发射的数据个数,然后发射这个结果Max — 计算并发射数据序列的最大值Min — 计算并发射数据序列的最小值Reduce — 按顺序对数据序列的每一个应用某个函数,然后返回这个值Sum — 计算并发射数据序列的和连接操作一些有精确可控的订阅行为的特殊ObservableConnect — 指示一个可连接的Observable开始发射数据给订阅者Publish — 将一个普通的Observable转换为可连接的RefCount — 使一个可连接的Observable表现得像一个普通的ObservableReplay — 确保所有的观察者收到同样的数据序列,即使他们在Observable开始发射数据之后才订阅转换操作To — 将Observable转换为其它的对象或数据结构Blocking 阻塞Observable的操作符操作符决策树几种主要的需求直接创建一个Observable(创建操作)组合多个Observable(组合操作)对Observable发射的数据执行变换操作(变换操作)从Observable发射的数据中取特定的值(过滤操作)转发Observable的部分值(条件/布尔/过滤操作)对Observable发射的数据序列求值(算术/聚合操作)
采纳率:94%
来自团队:
为您推荐:
其他类似问题
操作符的相关知识
换一换
回答问题,赢新手礼包
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。暂时是个Android程序猿。
View 利用 RxJava 的点击去抖动实现
最近项目里有个点赞的功能需求,发现当用户对一条评论反复点赞的速度过快时,会引起轻微的数据错乱。对一条评论不停的点赞,评论数本应在+1和-1之间不断循环的,但是点赞/取消点赞的时候需要进行网络请求,将点击结果上传到服务器,所以点多了之后,由于延时的原因,会出现某些+1或-1操作会失效,具体表现为,对一个初始赞数为0的评论进行这样一波疯狂操作后,点赞总数有可能变成负数,这无疑是不允许的。
至于要问有什么用户会这么无聊,跟打地鼠一样疯狂点赞,那当然非我们的测试小哥莫属了。
思考了各种解决方案的优劣后,最终采用了最省事的一个方法,给点赞按钮加个抖动阈值。正好最近写 RxJava 写得不亦乐乎,再加上不想为了这一处地方就引入 RxBinding 库,就想着自己写一下,于是顺手就写出了以下代码用以去除点击抖动:
mBinding.title.setOnClickListener(v -& {
Observable.create(e -& e.onNext(new Object()))
.throttleFirst(600, TimeUnit.MILLISECONDS)
.subscribe(o -& {
自己也没测就直接交给测试小哥了,然后被暴击:“你特么怎么该都没改就给我测了,这表现和之前没区别啊!?”
于是我瞬间懵比,自己一测,确实没解决问题,在代码里面写了俩 Log 一瞧,这去抖动根本没起任何作用啊,然后又找了网上几篇直接用 RxJava 实现的点击去抖动功能,发现我这也没写错啊。
左思右想,最后去看了下 RxBinding 的源码,才发现问题的关键所在。去抖动,需要将点击事件这个“事件”转换成一个Observable对象,而瞧我上面那段代码,直接在点击事件里生成的一个Observable对象,导致的结果就是每点击一次,就产生了一个新的 RxJava 流,所以导致没有达到想要的“只取600ms内第一次点击”的效果。
思考了下,转换成如下写法,总算达成效果了。
Observable.create((ObservableOnSubscribe&View&) e -&
mBinding.title.setOnClickListener(v -& e.onNext(v))
.throttleFirst(600, TimeUnit.MILLISECONDS)
.subscribe(view -& {
虽然 RxBinding 这个库也比较好用,但是我们的自定义控件好像就用不上它的,所以还是只能自己来实现去抖动了。
用 RxJava 实现的点击取抖动虽好,但有些情况还是顾不到,用它实现去抖动,就必须获取它的点击事件,将之转换成Observalbe对象才行,但有些情况这样是行不通的,比如一个页面的批量点击功能的实现:
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.check_for_update_button:
case R.id.clear_cache_button:
case R.id.save_flow_toggle:
case R.id.feedback_button:
case R.id.logout_button:
在这个里面就根本没法获取到点击事件对象,所以要么将这些控件单独拿出来一个个实现,要么就用土办法,在上次点击后一定时间内让点击事件无效化,可以用如下代码:
private static final int CLICK_INTERVAL = 600;
private static long lastClickT
public static boolean clickValid() {
long currentClickTime = System.currentTimeMillis();
return currentClickTime - lastClickTime & CLICK_INTERVAL;
如果只需要上述所有点击事件共用一个抖动时间,那么将上面这段代码写入统一的工具类,并在switch语句之前调用即可,也可以个性化定制每个View的抖动时间,那就写起来麻烦一点。在我看来是个“傻大黑粗”的方法。
没有更多推荐了,
加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!使用RxJava 提升用户体验 - 简书
使用RxJava 提升用户体验
原文链接:
网络永连接,服务器不出错,培根没肥肉
Search list animation by Daley P Maasz from Dribbble.
友好的用户体验通常是用户很爽,但开发者很痛苦。 当用户点击一个按钮后,因为后端没有及时响应而卡住界面,这回让用户很失望。
现在让我们创建一个更好的搜索功能(当用户在EditText输入文字时进行搜索):
尽可能少的网络请求
尽可能少的错误信息
Rx 的逻辑会十分简单并且针对小的细节
让我们从基本的逻辑开始:
让用户输入文字时执行网络请求,当结果返回时进行显示
RxTextView.textChanges(searchEditText)
.flatMap(Api::searchItems)
.subscribe(this::updateList, t-&showError());
1. 减少网络请求
有两个问题:
1 . 输入的文字每变化一个就执行请求(很垃圾),例如: 用户很快的输入 “a”(搜索”a“),然后”b”(搜索“ab”),然后“c”(搜索”abc”),然后又删除”c”(搜索“ab”), 输入“e”(搜索”abe”)。这个过程执行了5次请求。
2: 假如网络情况很差,多个线程同时进行,这时候就可能发生错误:如当用户输入“a”,然后”ab”,但是搜索“ab”的结果先返回了,之后返回了搜索“a的结果”,这时候输入框的文字是”ab”,但结果却是搜索“a”的
解决方案:
增加 throttling (节流)行为
debounce() 的作用正是如此, 时间为 100-150ms 为佳,如果服务器返回需要300ms,那么你可以在 500ms时更新UI界面
RxTextView.textChanges(searchEditText)
.debounce(150, MILLISECONDS)
.flatMap(Api::searchItems)
.subscribe(this::updateList, t-&showError());
取消之前的请求:
使用 switchMap来替代
flatMap, switchMap停止之前发出的请求, 假如在150ms的时候搜索”ab“,在300ms的时候搜索了”abcd“,但是搜索”ab”的请求需要花费超过150ms,那么搜索“abcd”的请求开始的时候将会取消上一个请求,用户只会获取到最后的一次搜索的结果。
RxTextView.textChanges(searchEditText)
.debounce(150, MILLISECONDS)
.switchMap(Api::searchItems)
.subscribe(this::updateList, t-&showError());
2. 没有错误提示/没有网络错误提示
如果网络请求失败,将不再观测文本的变化,(因为调用了OnError,整个事件流中断),这可以通过添加错误捕获函数轻松搞定
RxTextView.textChanges(searchEditText)
.debounce(150, MILLISECONDS)
.switchMap(Api::searchItems)
.onErrorResumeNext(t-& empty())
.subscribe(this::updateList);
但是不要这样做, 我们需要更好的处理方案。假如
searchItems() api因为网络连接而返回失败的呢? 或者是因为偶然的连接失败呢?
我们需要 重试机制 :
RxTextView.textChanges(searchEditText)
.debounce(150, MILLISECONDS)
.switchMap(Api::searchItems)
.retryWhen(new RetryWithConnectivity())
.subscribe(this::updateList, t-&showError());
如何进一步提升呢? 通过增加超时时间。 因为我们的 设计师 Lenzing 说 ”即使1s对用户来说也很漫长”,于是我们有做了下面的事情:
RxTextView.textChanges(searchEditText)
.debounce(150, MILLISECONDS)
.switchMap(Api::searchItems)
.retryWhen(new RetryWithConnectivityIncremental(context, 5, 15, SECONDS))
.subscribe(this::updateList, t-&showErrorToUser());
对比 RetryWithConnectivityIncremental
和 RetryWithConnectivity , RetryWithConnectivityIncremental 更智能,它首先设置了 5 秒的超时时间, 如果这个时候连接失败,如果用户再次重试的话,超时时间会被设置的更长(如上面的15 秒)。
现在,使用RxJava 提示了用户体验, 请求防抖动, 取消上次请求获取最新的结果, 智能的网络超时重试。这些可能用户不会注意到,但是这是一个好的设计 ;)
标签: AndroidRxjavaUX
demo的源码在
完整代码:
public class MainActivity extends AppCompatActivity {
@Bind(R.id.et_keyword) EditText et_
@Bind(R.id.tv_result)
TextView tv_
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
RestAdapter retrofit = new RestAdapter.Builder().setEndpoint("https://suggest.taobao.com")
.setLogLevel(RestAdapter.LogLevel.FULL)
.setConverter(new GsonConverter(new Gson()))
final SearchService service = retrofit.create(SearchService.class);
RxTextView.textChanges(et_keyword)
// 上面的对 tv_result 的操作需要在主线程
.subscribeOn(AndroidSchedulers.mainThread())
.debounce(600, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.filter(new Func1&CharSequence, Boolean&() {
@Override public Boolean call(CharSequence charSequence) {
// 清空搜索出来的结构
tv_result.setText("");
//当 EditText 中文字大于0的时候
return charSequence.length() & 0;
.switchMap(new Func1&CharSequence, Observable&Data&&() {
@Override public Observable&Data& call(CharSequence charSequence) {
return service.searchProdcut("utf-8", charSequence.toString());
.retryWhen(new RetryWithConnectivityIncremental(MainActivity.this, 5, 15, TimeUnit.MILLISECONDS))
// 网络操作在io线程
.subscribeOn(Schedulers.io())
//将 data 转换成 ArrayList&ArrayList&String&&
.map(new Func1&Data, ArrayList&ArrayList&String&&&() {
@Override public ArrayList&ArrayList&String&& call(Data data) {
return data.
// 将 ArrayList&ArrayList&String&& 中每一项提取出来 ArrayList&String&
.flatMap(new Func1&ArrayList&ArrayList&String&&, Observable&ArrayList&String&&&() {
@Override public Observable&ArrayList&String&& call(ArrayList&ArrayList&String&& arrayLists) {
return Observable.from(arrayLists);
.filter(new Func1&ArrayList&String&, Boolean&() {
@Override public Boolean call(ArrayList&String& strings) {
return strings.size() &= 2;
.map(new Func1&ArrayList&String&, String&() {
@Override public String call(ArrayList&String& strings) {
return "[商品名称:" + strings.get(0) + ", ID:" + strings.get(1) + "]\n";
// 发生错误后不要调用 onError,而是转到 onErrorResumeNext
/*.onErrorResumeNext(new Func1&Throwable, Observable&? extends String&&() {
@Override public Observable&? extends String& call(Throwable throwable) {
return Observable.just("error result");
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1&String&() {
@Override public void call(String charSequence) {
showpop(charSequence);
private void showpop(String result) {
tv_result.append(result);
interface SearchService {
@GET("/sug") Observable&Data& searchProdcut(@Query("code") String code, @Query("q") String keyword);
class Data {
public ArrayList&ArrayList&String&&
https://suggest.taobao.com/sug?code=utf-8&q=%E6%89%8B%E6%9C%BA
BroadcastObservable.java
package com.hanks.
import android.content.BroadcastR
import android.content.C
import android.content.I
import android.content.IntentF
import android.net.ConnectivityM
import android.net.NetworkI
import android.os.L
import rx.O
import rx.S
import rx.S
import rx.S
import rx.android.schedulers.AndroidS
import rx.functions.Action0;
import rx.subscriptions.S
* Created by hanks on 15-11-29.
public class BroadcastObservable implements Observable.OnSubscribe&Boolean& {
private final C
public BroadcastObservable(Context context) {
this.context =
public static Observable&Boolean& fromConnectivityManager(Context context) {
return Observable.create(new BroadcastObservable(context)).share();
private static Subscription unsubscribeInUiThread(final Action0 unsubscribe) {
return Subscriptions.create(new Action0() {
@Override public void call() {
if (Looper.getMainLooper() == Looper.myLooper()) {
unsubscribe.call();
final Scheduler.Worker inner = AndroidSchedulers.mainThread().createWorker();
inner.schedule(new Action0() {
@Override public void call() {
unsubscribe.call();
inner.unsubscribe();
@Override public void call(final Subscriber&? super Boolean& subscriber) {
final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override public void onReceive(Context context, Intent intent) {
subscriber.onNext(isConnectedToInternet());
context.registerReceiver(receiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
subscriber.add(unsubscribeInUiThread(new Action0() {
@Override public void call() {
context.unregisterReceiver(receiver);
private Boolean isConnectedToInternet() {
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
return networkInfo != null && networkInfo.isConnected();
RetryWithConnectivityIncremental.java
package com.hanks.
import android.content.C
import java.util.concurrent.TimeU
import java.util.concurrent.TimeoutE
import retrofit.RetrofitE
import rx.O
import rx.functions.Action1;
import rx.functions.Func1;
* RetryWithConnectivityIncremental
* Created by hanks on 15-11-29.
public class RetryWithConnectivityIncremental implements Func1&Observable&? extends Throwable&, Observable&?&& {
private final int startTimeO
private final int maxTimeO
private final TimeUnit timeU
private int timeO
private Observable&Boolean& isC
public RetryWithConnectivityIncremental(Context context, int startTimeOut, int maxTimeOut, TimeUnit timeUnit) {
this.startTimeOut = startTimeO
this.maxTimeOut = maxTimeO
this.timeOut = startTimeO
this.timeUnit = timeU
isConnected = getConnectedObservable(context);
private Observable&Boolean& getConnectedObservable(Context context) {
return BroadcastObservable.fromConnectivityManager(context)
.distinctUntilChanged()
.filter(new Func1&Boolean, Boolean&() {
@Override public Boolean call(Boolean isConnected) {
return isC
private Observable.Transformer&Boolean, Boolean& attachIncementalTimeOut() {
return new Observable.Transformer&Boolean, Boolean&() {
@Override public Observable&Boolean& call(Observable&Boolean& observable) {
return observable.timeout(timeOut, timeUnit).doOnError(new Action1&Throwable&() {
@Override public void call(Throwable throwable) {
if (throwable instanceof TimeoutException) {
timeOut = timeOut & maxTimeOut ? maxTimeOut : timeOut + startTimeO
@Override public Observable&?& call(final Observable&? extends Throwable& observable) {
return observable.flatMap(new Func1&Throwable, Observable&Boolean&&() {
@Override public Observable&Boolean& call(Throwable throwable) {
if (throwable instanceof RetrofitError && ((RetrofitError) throwable).getKind() == RetrofitError.Kind.NETWORK) {
return isC
return Observable.error(throwable);
}).compose(attachIncementalTimeOut());
Android developer
Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智能路由,微代理,控制总线)。分布式系统的协调导致了样板模式, 使用Spring Cloud开发人员可以快速地支持实现这些模式的服务和应用程序。他们将在任何分布式...
百战程序员_ Java1573题 QQ群:034603 掌握80%年薪20万掌握50%年薪10万 全程项目穿插, 从易到难,含17个项目视频和资料持续更新,请关注www.itbaizhan.com 国内最牛七星级团队马士兵、高淇等11位十年开发经验专...
国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿: 前言: 排版 by Dr_Ting公众号:庭说移步 tingtalk.me 获得更友好的阅读体验 Q/GDW XXXX-201X《面向对象的用电信息数据交换协议》是根据《国家...
API定义规范 本规范设计基于如下使用场景: 请求频率不是非常高:如果产品的使用周期内请求频率非常高,建议使用双通道的长连接,例如 socket/websocket,避免不必要的请求头浪费资源以及重建连接带来的性能损耗; 服务器不需要主动推送消息:如果需要服务器主动发起推送...
RxJava系列文章目录导读: 一、RxJava create操作符的用法和源码分析二、RxJava map操作符用法详解三、RxJava flatMap操作符用法详解四、RxJava concatMap操作符用法详解五、RxJava onErrorResumeNext操作...
这个夏天,我与西瓜有个约定: 约定了晚饭是它 这个夏天,我与学校有个约定: 约定了暑假陪着它 这个夏天,我与家人有个约定: 约定了暑假兼职。 这个夏天,我想与太阳有个约定: 约定太阳不要一直晒我 但是太阳没有答应我的约定 还是一直晒一直晒 稍稍有点难过
尚书堂第二届师资培训开始的第一天,第一节课,第一项流程就是……端着一满满的水围着教室走一圈 心想:第一节开始就是端水?为什么要端水? 陈老师说了一下,学习书法众所周知,是修身养性。是不是拿毛笔随便写一下就可以做到修身养性了呢?不,这些只是前提的工具。今天我们来练定性——静 ...
萧山化妆培训,你也可以成为化妆大师 如今流行着这么一句话“化妆好比整容”,虽然有些夸张,但是拥有出神入化的化妆术的人往往能让自己的精神面貌甚至是五官有不一样的改变。化妆师也一度成为了受大众欢迎的热门行业。 小班教学,以教学为本 化妆师是操作性极强的职业,想要拥有一双巧手,随...
中医的理论基础里,五行是我们众所周知的 从“五行”的整体概念出发来理解,“金木水火土”等自然界五种元素,五种元素间有着相生相克的辩证关系。这个五行生克的理论为传统中医的“情志致病论”奠定了基础 《黄帝内经》上从临床病理学的角度总结出了一套脏腑和情绪的相关规律,在人类历史上首...
雨,又来了。还未走出家门,就听到那“沙沙”的雨声,密密的、满满的。似乎雨声不应该用“满”这个字来形容,但不知为何,一听到它“沙沙”的响声,脑海中就不由自主的浮现出了这个词,把我的心也填的满满的,好似快要一出来了一样。走到外面,我深吸了一口气,啊,满是雨水的气息,那么的清新...

我要回帖

更多关于 ios 避免按钮重复点击 的文章

 

随机推荐