一、事件分发

有一段时间没更博客了,想象我写博客的初衷主要有2个原因:

  1. 如果有人看到了,从中找到一点能帮助到他的东西,这样最好不过了。
  2. 写给自己看。我相信整理一遍写一遍对自己的理解和认知有很大的帮助。

最近闭关修炼,哈哈哈,说的有点魔幻。主要最近对安卓的看法有了一点新的看法,看了一个公众号的推送,有个人将安卓的学习和进阶分为了5个部分:

  1. UI
  2. 性能
  3. NDk
  4. 架构
  5. 其他

这样分是基于项目而言的,不同类型不同定位的项目这5个部分所占的比重也是不一样的。比如有些项目就要UI好看,用户看你UI好看就会极大的提升兴趣。

当然我最近学习这些东西不是因为看了这个公众号然后就去学UI相关的东西,而是我在学的过程中发现了这个公众号,我就觉得这样分也比较合理,就顺带提了一下。

回归主题:今天我要说的是我学习的第一个东西:

事件分发

之前一直学习这块,但是也是含含糊糊,这次静下心来好好的梳理了一遍,算是掌握了7-8分吧,但还有几个问题没弄明白,百度了也没百到,问了大神也没得到我想要的答案,然后就不了了之了。这些问题我记在心中,我相信在不远的将来肯定会遇到我的师傅,他肯定会为我传道解惑。

好了下面开始我的表演:

我之前只知道事件分发是从父view传递到子view,但是我不知道起点是那里。最近看了一篇博客,链接忘记了,我感到很羞耻,其实以我的水平我应该能想到是Activity,但是之前我没想到,但我现在知道了,哈哈哈。
为什么说是Activity呢,因为Activity是负责与用户交互的啊,那么我的手势操作不应该被Activity所捕捉然后进行下一步吗?
那么Activity是怎么捕捉到我们的手势的呢?这个我不知道,跟硬件或底层实现有关了,我只知道最终会从来到Activity的dispatchTouchEvent()方法:

  1. public boolean dispatchTouchEvent(MotionEvent ev) {  
  2.   
  3.         //如果是按下状态就调用onUserInteraction()方法,onUserInteraction()方法  
  4.         //是个空的方法, 我们直接跳过这里看下面的实现  
  5.         if (ev.getAction() == MotionEvent.ACTION_DOWN) {  
  6.             onUserInteraction();  
  7.         }  
  8.           
  9.         if (getWindow().superDispatchTouchEvent(ev)) {  
  10.             return true;  
  11.         }  
  12.           
  13.         //getWindow().superDispatchTouchEvent(ev)返回false,这个事件就交给Activity  
  14.         //来处理, Activity的onTouchEvent()方法直接返回了false  
  15.         return onTouchEvent(ev);  
  16.     }

根据注释我们可以看到,第9行如果返回true就说明有view能处理我们的事件,那么就不会调用Activity的onTouchEvent()方法;如果第9行返回false,那么就说明没有view能响应我们的事件,那么事件就交给Activity处理。

我们进入第9行的getWindow().superDispatchTouchEvent(ev)方法内部:
它是调用Window类的superDispatchTouchEvent(ev)方法,但是我们进入Window类发现这个方法是个抽象方法,而Window也是一个抽象类。Window就是我们的窗口,就是最顶层的一个承载窗体view的东西。他只有一个子类PhoneWindow,我们直接进入PhoneWindow类的superDispatchTouchEvent(ev)方法:

@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

mDecor是什么呢?他是DecorView的实例,这个DecorView是一个继承FrameLayout的的ViewGroup,在之前的版本中,它是作为PhoneWindow的内部类的形式出现,在之后的版本中它被独立出来作为一个单独的类。他的作用就是存放我们在Activity中设置的布局View,也就是说我们在Activity中通过setContentView(int resid)方法设置的布局,最终会绑定到DecorView上。看一张图:
这里写图片描述

图中DecorView中还有一个LinearLayout,因为要存放2个FrameLayout:一个是顶部的ActionBar,一个是我们给activity设置的布局。
好了,我们继续看DecorView类中的superDispatchTouchEvent(event)方法:

public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

调用的是父类的dispatchTouchEvent(event),我们知道他是继承FrameLayout的,所以我们去ViewGroup中看dispatchTouchEvent(event)方法:

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        // If the event targets the accessibility focused view and this is it, start
        // normal event dispatch. Maybe a descendant is what will handle the click.
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.
            //预处理下
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // Check for interception.
            //检查是否拦截事件
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }

            // If intercepted, start normal event dispatch. Also if there is already
            // a view that is handling the gesture, do normal event dispatch.
            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }

            // Check for cancelation.
            //cancle检查
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            // Update list of touch targets for pointer down, if needed.
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            // 1、这个if是在没有拦截也没有取消事件时执行的
            if (!canceled && !intercepted) {

                // If the event is targeting accessiiblity focus we give it to the
                // view that has accessibility focus and if it does not handle it
                // we clear the flag and dispatch the event to all children as usual.
                // We are looking up the accessibility focused host to avoid keeping
                // state since these events are very rare.
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;
                //从down开始
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    final int actionIndex = ev.getActionIndex(); // always 0 for down
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                    // Clean up earlier touch targets for this pointer id in case they
                    // have become out of sync.
                    //移除之前点击的这个点的位置的一些操作
                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                    //拿到child
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        //这句话的意思就是根据用户的触摸位置找到相应的子view,添加进集合中,然后根据子view的count数反向遍历,为什么要反向遍历,因为view是一层一层覆盖的啊(比如Relativelayout)。然后我们找到最上层的子view,让他去相应事件
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                            // If there is a view that has accessibility focus we want it
                            // to get the event first and if not handled we will perform a
                            // normal dispatch. We may do a double iteration but this is
                            // safer given the timeframe.
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }
                            //这个newTouchTarget就是在链表里找到的接收事件的view对应的newTouchTarget ,这个newTouchTarget 应该包含了一些信息吧,如果不=null,说明这个view已经接收到事件了
                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                // Child is already receiving touch within its bounds.
                                // Give it the new pointer in addition to the ones it is handling.
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            //这个if方法里面就是去调用该子view的dispatchtouchevent方法,下面会贴源码继续分析
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                //如果这个子view的dispatchtouchevent返回true ,应该这一套下来有一个不管是view还是ViewGroup能处理事件才会返回true
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    // childIndex points into presorted list, find original index
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }
                    //没找到接收事件的child
                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        // Did not find a child to receive the event.
                        // Assign the pointer to the least recently added target.
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }
            //2、接下来的if else逻辑是当拦截了或者是cancle事件时执行。
            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
            //拦截了,就自己去处理
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
            //这里应该是cancle事件吧
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

            // Update list of touch targets for pointer up or cancel, if needed.
            if (canceled
                    || actionMasked == MotionEvent.ACTION_UP
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                resetTouchState();
            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
                final int actionIndex = ev.getActionIndex();
                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                removePointersFromTouchTargets(idBitsToRemove);
            }
        }

        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

        // Calculate the number of pointers to deliver.
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        // If for some reason we ended up in an inconsistent state where it looks like we
        // might produce a motion event with no pointers in it, then drop the event.
        if (newPointerIdBits == 0) {
            return false;
        }

        // If the number of pointers is the same and we don't need to perform any fancy
        // irreversible transformations, then we can reuse the motion event for this
        // dispatch as long as we are careful to revert any changes we make.
        // Otherwise we need to make a copy.
        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }

        // Perform any necessary transformations and dispatch.
        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }

            handled = child.dispatchTouchEvent(transformedEvent);
        }

        // Done.
        transformedEvent.recycle();
        return handled;
    }

新版API(我这是API26)做了一些改动,有些东西提出去做了,有些东西整合进来了。分析起来有点吃力,之前看的是老版本的API,简单一些,但主体都差不多。

总结一下:对于ViewGroup的dispatchTouchEvent方法:

首先看down是否拦截,如果没拦截,根据我们点击的位置找到子view,然后调用它的dispatchTouchEvent方法,如果这个子view的dispatchTouchEvent方法返回true,那么ViewGroup也返回true。如果子viewdispatchTouchEvent返回false,代表不能处理事件,那么对于ViewGroup而言就要自己处理了,他会先判断是是cancle还是up事件,如果是cancle或者up得话,就将拦截事件的标志置为默认的false(可以理解当我们设置了拦截事件后,我们抬起手指或取消touch的时候会将这个标志置为false。所以说disallowIntercept在我们每次down的时候都是false。)。然后因为ViewGroup自己处理,就要调用ViewGroup的super.dispatchtouchevent方法,如果是up或者cancle,那么就传入up或cancle事件,并返回。下面还有,下面的逻辑down事件不会走,move和up可能会走到这里,如果move或up拦截了事件的话,那么就将cancle事件交给子view,ViewGroup返回true,表示消费了事件。

如果调用,那么最终会调用到我们想要点击的子view,执行他的dispatchtouchevent方法。然后根据返回情况向上返回,就是ViewGroup的dispatchtouchevent返回值是根据其子view的dispatchtouchevent返回值而定的。
有一张图:
这里写图片描述

view的dispatchtouchevent方法:

public boolean dispatchTouchEvent(MotionEvent event) {  
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
            mOnTouchListener.onTouch(this, event)) {  
        return true;  
    }  
    return onTouchEvent(event);  
}

如果view设置了touchlistener,enable,而且ontouch方法返回了true的话,就不会执行ontouchevent了,那么点击事件就无法执行了。否则就执行ontouchevent方法。
我们看onTouchEvent方法:

  1. public boolean onTouchEvent(MotionEvent event) {  
  2.       final int viewFlags = mViewFlags;  
  3.   
  4.       if ((viewFlags & ENABLED_MASK) == DISABLED) {  
  5.           return (((viewFlags & CLICKABLE) == CLICKABLE ||  
  6.                   (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  
  7.       }  
  8.   
  9.       //如果设置了Touch代理,就交给代理来处理,mTouchDelegate默认是null  
  10.       if (mTouchDelegate != null) {  
  11.           if (mTouchDelegate.onTouchEvent(event)) {  
  12.               return true;  
  13.           }  
  14.       }  
  15.   
  16.       //如果View是clickable或者longClickable的onTouchEvent就返回true, 否则返回false  
  17.       if (((viewFlags & CLICKABLE) == CLICKABLE ||  
  18.               (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
  19.           switch (event.getAction()) {  
  20.               case MotionEvent.ACTION_UP:  
  21.                   boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
  22.                   if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  
  23.                       boolean focusTaken = false;  
  24.                       if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  
  25.                           focusTaken = requestFocus();  
  26.                       }  
  27.   
  28.                       if (!mHasPerformedLongPress) {  
  29.                           removeLongPressCallback();  
  30.   
  31.                           if (!focusTaken) {  
  32.                               if (mPerformClick == null) {  
  33.                                   mPerformClick = new PerformClick();  
  34.                               }  
  35.                               if (!post(mPerformClick)) {  
  36.                                   performClick();  
  37.                               }  
  38.                           }  
  39.                       }  
  40.   
  41.                       if (mUnsetPressedState == null) {  
  42.                           mUnsetPressedState = new UnsetPressedState();  
  43.                       }  
  44.   
  45.                       if (prepressed) {  
  46.                           mPrivateFlags |= PRESSED;  
  47.                           refreshDrawableState();  
  48.                           postDelayed(mUnsetPressedState,  
  49.                                   ViewConfiguration.getPressedStateDuration());  
  50.                       } else if (!post(mUnsetPressedState)) {  
  51.                           mUnsetPressedState.run();  
  52.                       }  
  53.                       removeTapCallback();  
  54.                   }  
  55.                   break;  
  56.   
  57.               case MotionEvent.ACTION_DOWN:  
  58.                   if (mPendingCheckForTap == null) {  
  59.                       mPendingCheckForTap = new CheckForTap();  
  60.                   }  
  61.                   mPrivateFlags |= PREPRESSED;  
  62.                   mHasPerformedLongPress = false;  
  63.                   postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
  64.                   break;  
  65.   
  66.               case MotionEvent.ACTION_CANCEL:  
  67.                   mPrivateFlags &= ~PRESSED;  
  68.                   refreshDrawableState();  
  69.                   removeTapCallback();  
  70.                   break;  
  71.   
  72.               case MotionEvent.ACTION_MOVE:  
  73.                   final int x = (int) event.getX();  
  74.                   final int y = (int) event.getY();  
  75.   
  76.                   //当手指在View上面滑动超过View的边界,  
  77.                   int slop = mTouchSlop;  
  78.                   if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
  79.                           (y < 0 - slop) || (y >= getHeight() + slop)) {  
  80.                       // Outside button  
  81.                       removeTapCallback();  
  82.                       if ((mPrivateFlags & PRESSED) != 0) {  
  83.                           removeLongPressCallback();  
  84.   
  85.                           mPrivateFlags &= ~PRESSED;  
  86.                           refreshDrawableState();  
  87.                       }  
  88.                   }  
  89.                   break;  
  90.           }  
  91.           return true;  
  92.       }  
  93.   
  94.       return false;  
  95.   } 

这个就是处理点击事件了,长点击在down里面执行,短点击在up里执行。
down里面会发送一个post延迟消息,用来判断是否是长点击事件即是否达到系统设置的长点击事件时间,而且要设置了onLongClickListener的话,达到事件后就去执行长点击事件,如果onLongClick返回了true,那么会有一个标志mHasPerformedLongPress被置为true,相当于表示事件被消费了。在up事件中if会对这个标志取反,如果被消费了,那么就不会进入if里面,那么点击事件不会得到执行了。但是up这个时间还是会执行的,只不过不执行点击事件了。

事件分发应该就是这样大致的流程,有几点注意:

  1. 我们重写拦截事件的方法去拦截事件,默认拦截的是down事件。如果想拦截其他事件,那么就需要在方法里面对ev进行判断然后进行拦截操作。
  2. 如果在ViewGroup中对down拦截了,那么就会调用其的super.dispatchtouchevent方法,也就是view的,但是ViewGroup是没有设置ontouchlistener的,而且默认不能点击,所以根据情况自己去做相应的处理,默认不做处理的话就直接返回false了。
  3. 如果对move或者up事件进行拦截的话,子view得不到这些事件,点击事件无法执行,但是长点击事件是可以执行的,因为长点击是在down里面做的,这种情况ViewGroup就会将cancle交给子view,并直接返回true。

就到这里为止吧,以后项目中遇到什么问题再来补充,学习的过程也是一个不断纠正错误的过程。

下一篇我要写从启动Activity到用户可以点击view的这个过程发生了什么。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自动控制节水灌溉技术的高低代表着农业现代化的发展状况,灌溉系统自动化水平较低是制约我国高效农业发展的主要原因。本文就此问题研究了单片机控制的滴灌节水灌溉系统,该系统可对不同土壤的湿度进行监控,并按照作物对土壤湿度的要求进行适时、适量灌水,其核心是单片机和PC机构成的控制部分,主要对土壤湿度与灌水量之间的关系、灌溉控制技术及设备系统的硬件、软件编程各个部分进行了深入的研究。 单片机控制部分采用上下位机的形式。下位机硬件部分选用AT89C51单片机为核心,主要由土壤湿度传感器,信号处理电路,显示电路,输出控制电路,故障报警电路等组成,软件选用汇编语言编程。上位机选用586型以上PC机,通过MAX232芯片实现同下位机的电平转换功能,上下位机之间通过串行通信方式进行数据的双向传输,软件选用VB高级编程语言以建立友好的人机界面。系统主要具有以下功能:可在PC机提供的人机对话界面上设置作物要求的土壤湿度相关参数;单片机可将土壤湿度传感器检测到的土壤湿度模拟量转换成数字量,显示于LED显示器上,同时单片机可采用串行通信方式将此湿度值传输到PC机上;PC机通过其内设程序计算出所需的灌水量和灌水时间,且显示于界面上,并将有关的灌水信息反馈给单片机,若需灌水,则单片机系统启动鸣音报警,发出灌水信号,并经放大驱动设备,开启电磁阀进行倒计时定时灌水,若不需灌水,即PC机上显示的灌水量和灌水时间均为0,系统不进行灌水。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值