package com.itcast.googleplayteach.ui.widget;import android.content.Context;import android.graphics.Canvas;import android.graphics.Paint;import android.graphics.Typeface;import android.support.v4.view.ViewCompat;import android.support.v4.view.ViewPager;import android.support.v4.view.ViewPager.OnPageChangeListener;import android.support.v4.widget.EdgeEffectCompat;import android.support.v4.widget.ScrollerCompat;import android.util.AttributeSet;import android.util.TypedValue;import android.view.Gravity;import android.view.MotionEvent;import android.view.VelocityTracker;import android.view.View;import android.view.ViewConfiguration;import android.view.ViewGroup;import android.view.ViewParent;import android.view.ViewTreeObserver;import android.widget.ImageButton;import android.widget.TextView;import com.itcast.googleplayteach.R;import com.itcast.googleplayteach.ui.activity.BaseActivity;import com.itcast.googleplayteach.utils.UIUtils;public class PagerTab extends ViewGroup {    private ViewPager mViewPager;    private PageListener mPageListener = new PageListener();// 用于注册给ViewPager监听状态和滚动    private OnPageChangeListener mDelegatePageListener;// 用于通知外界ViewPager的状态和滚动    private BaseActivity mActivity;    private int mDividerPadding = 12;// 分割线上下的padding    private int mDividerWidth = 1;// 分割线的宽度    private int mDividerColor = 0x1A000000;// 分割线颜色    private Paint mDividerPaint;// 分割线的画笔    private int mIndicatorHeight = 4;// 指示器的高度    private int mIndicatorWidth;// 指示器的宽度,是动态的随着tab的宽度变化    private int mIndicatorLeft;// 指示器的距离左边的距离    private int mIndicatorColor = 0xFF0084FF;// 指示器颜色    private Paint mIndicatorPaint; // 指示器的画笔    private int mContentWidth;// 记录自身内容的宽度    private int mContentHeight;// 记录自身内容的高度    private int mTabPadding = 24;// tab左右的内边距    private int mTabTextSize = 16; // tab文字大小    private int mTabBackgroundResId = R.drawable.bg_tab_text;// tab背景资源    private int mTabTextColorResId = R.color.tab_text_color; // tab文字颜色    private int mTabCount;// tab的个数    private int mCurrentPosition = 0;// 当前光标所处的tab,规则是以光标的最左端所在的item的position    private float mCurrentOffsetPixels;// 光标左边距离当前光标所处的tab的左边距离    private int mSelectedPosition = 0; // 当前被选中的tab,用于记录手指点击tab的position    private boolean mIsBeingDragged = false;// 是否处于拖动中    private float mLastMotionX;// 上一次手指触摸的x坐标    private VelocityTracker mVelocityTracker;// 用于记录速度的帮助类    private int mMinimumVelocity;// 系统默认的最小满足fling的速度    private int mMaximumVelocity;// 系统默认最大的fling速度    private int mTouchSlop;// 系统默认满足滑动的最小位移    private ScrollerCompat mScroller;// 处理滚动的帮助者    private int mLastScrollX;// 记录上一次滚动的x位置,这是用于处理overScroll,实际位置可能会受到限制    private int mMaxScrollX = 0;// 控件最大可滚动的距离    private int mSplitScrollX = 0;// 根据item的个数,计算出每移动一个item控件需要移动的距离    private EdgeEffectCompat mLeftEdge;// 处理overScroll的反馈效果    private EdgeEffectCompat mRightEdge;    public PagerTab(Context context) {        this(context, null);    }    public PagerTab(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public PagerTab(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        if (context instanceof BaseActivity) {            mActivity = (BaseActivity) context;        }        init();        initPaint();    }    /** 初始化一些常量 */    private void init() {        // 把一个值从dip转换成px        mIndicatorHeight = UIUtils.dip2px(mIndicatorHeight);        mDividerPadding = UIUtils.dip2px(mDividerPadding);        mTabPadding = UIUtils.dip2px(mTabPadding);        mDividerWidth = UIUtils.dip2px(mDividerWidth);        mTabTextSize = UIUtils.dip2px(mTabTextSize);        // 创建一个scroller        mScroller = ScrollerCompat.create(mActivity);        // 获取一个系统关于View的常量配置类        final ViewConfiguration configuration = ViewConfiguration                .get(mActivity);        // 获取滑动的最小距离        mTouchSlop = configuration.getScaledTouchSlop();        // 获取fling的最小速度        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();        // 获取fling的最大速度        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();        mLeftEdge = new EdgeEffectCompat(mActivity);        mRightEdge = new EdgeEffectCompat(mActivity);    }    /** 初始化笔 */    private void initPaint() {        mIndicatorPaint = new Paint();        mIndicatorPaint.setAntiAlias(true);        mIndicatorPaint.setStyle(Paint.Style.FILL);        mIndicatorPaint.setColor(mIndicatorColor);        mDividerPaint = new Paint();        mDividerPaint.setAntiAlias(true);        mDividerPaint.setStrokeWidth(mDividerWidth);        mDividerPaint.setColor(mDividerColor);    }    /** 设置ViewPager */    public void setViewPager(ViewPager viewPager) {        if (viewPager == null || viewPager.getAdapter() == null) {            throw new IllegalStateException(                    "ViewPager is null or ViewPager does not have adapter instance.");        }        mViewPager = viewPager;        onViewPagerChanged();    }    private void onViewPagerChanged() {        mViewPager.setOnPageChangeListener(mPageListener);// 给ViewPager设置监听        mTabCount = mViewPager.getAdapter().getCount();// 有多少个tab需要看ViewPager有多少个页面        for (int i = 0; i < mTabCount; i++) {            if (mViewPager.getAdapter() instanceof IconTabProvider) {// 如果想要使用icon作为tab,则需要adapter实现IconTabProvider接口                addIconTab(i,                        ((IconTabProvider) mViewPager.getAdapter())                                .getPageIconResId(i));            } else {                addTextTab(i, mViewPager.getAdapter().getPageTitle(i)                        .toString());            }        }        ViewTreeObserver viewTreeObserver = getViewTreeObserver();        if (viewTreeObserver != null) {// 监听第一个的全局layout事件,来设置当前的mCurrentPosition,显示对应的tab            viewTreeObserver                    .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {                        @Override                        public void onGlobalLayout() {                            getViewTreeObserver().removeGlobalOnLayoutListener(                                    this);// 只需要监听一次,之后通过listener回调即可                            mCurrentPosition = mViewPager.getCurrentItem();                            if (mDelegatePageListener != null) {                                mDelegatePageListener                                        .onPageSelected(mCurrentPosition);                            }                        }                    });        }    }    /** 设置监听,因为Tab会监听ViewPager的状态,所以不要给ViewPager设置监听了,设置给Tab,由Tab转发 */    public void setOnPageChangeListener(OnPageChangeListener listener) {        mDelegatePageListener = listener;    }    /** 添加文字tab */    private void addTextTab(final int position, String title) {        TextView tab = new TextView(mActivity);        tab.setText(title);        tab.setGravity(Gravity.CENTER);        tab.setSingleLine();        tab.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTabTextSize);        tab.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));        tab.setTextColor(UIUtils.getColorStateList(mTabTextColorResId));        tab.setBackgroundDrawable(UIUtils.getDrawable(mTabBackgroundResId));        tab.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,                LayoutParams.MATCH_PARENT));        addTab(position, tab);    }    /** 添加图片icon */    private void addIconTab(final int position, int resId) {        ImageButton tab = new ImageButton(mActivity);        tab.setImageResource(resId);        tab.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,                LayoutParams.WRAP_CONTENT));        addTab(position, tab);    }    private void addTab(final int position, View tab) {        tab.setFocusable(true);        // 设置tab的点击事件,当tab被点击时候切换pager的页面        tab.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View v) {                mViewPager.setCurrentItem(position);            }        });        tab.setPadding(mTabPadding, 0, mTabPadding, 0);        addView(tab, position);    }    /** 测量时的回调 */    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        // 获取控件自身的宽高,模式        int widthSize = MeasureSpec.getSize(widthMeasureSpec)                - getPaddingLeft() - getPaddingRight();        int heightSize = MeasureSpec.getSize(heightMeasureSpec)                - getPaddingBottom() - getPaddingBottom();        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        int totalWidth = 0;        int highest = 0;        int goneChildCount = 0;        for (int i = 0; i < mTabCount; i++) {            final View child = getChildAt(i);            if (child == null || child.getVisibility() == View.GONE) {                goneChildCount--;                continue;            }            int childWidthMeasureSpec;            int childHeightMeasureSpec;            LayoutParams childLayoutParams = child.getLayoutParams();            if (childLayoutParams == null) {                childLayoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,                        LayoutParams.WRAP_CONTENT);            }            if (childLayoutParams.width == LayoutParams.MATCH_PARENT) {                childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize,                        MeasureSpec.EXACTLY);            } else if (childLayoutParams.width == LayoutParams.WRAP_CONTENT) {                childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize,                        MeasureSpec.AT_MOST);            } else {                childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(                        childLayoutParams.width, MeasureSpec.EXACTLY);            }            if (childLayoutParams.height == LayoutParams.MATCH_PARENT) {                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(                        heightSize, MeasureSpec.EXACTLY);            } else if (childLayoutParams.height == LayoutParams.WRAP_CONTENT) {                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(                        heightSize, MeasureSpec.AT_MOST);            } else {                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(                        childLayoutParams.height, MeasureSpec.EXACTLY);            }            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);            int childWidth = child.getMeasuredWidth();            int childHeight = child.getMeasuredHeight();            totalWidth += childWidth;            highest = highest < childHeight ? childHeight : highest;        }        if (totalWidth <= widthSize) {// 如果子Tab的总宽度小于PagerTab,则采用平分模式            int splitWidth = (int) (widthSize                    / (mTabCount - goneChildCount + 0.0f) + 0.5f);            for (int i = 0; i < mTabCount; i++) {                final View child = getChildAt(i);                if (child == null || child.getVisibility() == View.GONE) {                    continue;                }                int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(                        splitWidth, MeasureSpec.EXACTLY);                int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(                        child.getMeasuredHeight(), MeasureSpec.EXACTLY);                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);            }            mMaxScrollX = 0;            mSplitScrollX = 0;        } else {// 如果所有子View大于控件的宽度            mMaxScrollX = totalWidth - widthSize;            mSplitScrollX = (int) (mMaxScrollX                    / (mTabCount - goneChildCount - 1.0f) + 0.5f);        }        if (widthMode == MeasureSpec.EXACTLY) {            mContentWidth = widthSize;        } else {            mContentWidth = totalWidth;        }        if (heightMode == MeasureSpec.EXACTLY) {            mContentHeight = heightSize;        } else {            mContentHeight = highest;        }        int measureWidth = mContentWidth + getPaddingLeft() + getPaddingRight();        int measureHeight = mContentHeight + getPaddingTop()                + getPaddingBottom();        setMeasuredDimension(measureWidth, measureHeight);    }    /** 布局时的回调 */    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {// 这里简化了,没有考虑margin的情况        if (changed) {            int height = b - t;// 控件供子View显示的高度            int left = l;            for (int i = 0; i < mTabCount; i++) {                final View child = getChildAt(i);                if (child == null || child.getVisibility() == View.GONE) {                    continue;                }                int top = (int) ((height - child.getMeasuredHeight()) / 2.0f + 0.5f);// 如果控件比tab要高,则居中显示                int right = left + child.getMeasuredWidth();                child.layout(left, top, right, top + child.getMeasuredHeight());// 摆放tab                left = right;// 因为是水平摆放的,所以为下一个准备left值            }        }    }    /** 绘制时的回调 */    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        final int height = getHeight();        // 画指示器        canvas.drawRect(mIndicatorLeft, height - mIndicatorHeight,                mIndicatorLeft + mIndicatorWidth, height, mIndicatorPaint);        // 画分割线        for (int i = 0; i < mTabCount - 1; i++) {// 分割线的个数比tab的个数少一个            final View child = getChildAt(i);            if (child == null || child.getVisibility() == View.GONE) {                continue;            }            if (child != null) {                canvas.drawLine(child.getRight(), mDividerPadding,                        child.getRight(), mContentHeight - mDividerPadding,                        mDividerPaint);            }        }        // 因为overScroll效果是一个持续效果,所以需要持续画        boolean needsInvalidate = false;        if (!mLeftEdge.isFinished()) {// 如果效果没停止            final int restoreCount = canvas.save();// 先保存当前画布            final int heightEdge = getHeight() - getPaddingTop()                    - getPaddingBottom();            final int widthEdge = getWidth();            canvas.rotate(270);            canvas.translate(-heightEdge + getPaddingTop(), 0);            mLeftEdge.setSize(heightEdge, widthEdge);            needsInvalidate |= mLeftEdge.draw(canvas);            canvas.restoreToCount(restoreCount);        }        if (!mRightEdge.isFinished()) {            final int restoreCount = canvas.save();            final int widthEdge = getWidth();            final int heightEdge = getHeight() - getPaddingTop()                    - getPaddingBottom();            canvas.rotate(90);            canvas.translate(-getPaddingTop(), -(widthEdge + mMaxScrollX));            mRightEdge.setSize(heightEdge, widthEdge);            needsInvalidate |= mRightEdge.draw(canvas);            canvas.restoreToCount(restoreCount);        }        if (needsInvalidate) {            postInvalidate();        }    }    /** 触摸事件是否拦截的方法 */    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        final int action = ev.getAction();        if (mIsBeingDragged && action == MotionEvent.ACTION_MOVE) {// 当已经处于拖动,并且当前事件是MOVE,直接消费掉            return true;        }        switch (action) {        case MotionEvent.ACTION_DOWN: {            final float x = ev.getX();            mLastMotionX = x; // 记录住当前的x坐标            mIsBeingDragged = !mScroller.isFinished();// 如果按下的时候还在滚动,则把状态处于拖动状态            break;        }        case MotionEvent.ACTION_MOVE: {            final float x = ev.getX();            final int xDiff = (int) Math.abs(x - mLastMotionX);// 计算两次的差值            if (xDiff > mTouchSlop) {// 如果大于最小移动的距离,则把状态改变为拖动状态                mIsBeingDragged = true;                mLastMotionX = x;                ViewParent parent = getParent();// 并请求父View不要再拦截自己触摸事件,交给自己处理                if (parent != null) {                    parent.requestDisallowInterceptTouchEvent(true);                }            }            break;        }        case MotionEvent.ACTION_CANCEL:// 当手指离开或者触摸事件取消的时候,把拖动状态取消掉        case MotionEvent.ACTION_UP:            mIsBeingDragged = false;            break;        }        return mIsBeingDragged;// 如果是拖动状态,则拦截事件,交给自己的onTouch处理    }    /** 触摸事件的处理方法 */    public boolean onTouchEvent(MotionEvent ev) {        if (mVelocityTracker == null) {            mVelocityTracker = VelocityTracker.obtain();        }        mVelocityTracker.addMovement(ev);        final int action = ev.getAction();        switch (action) {        case MotionEvent.ACTION_DOWN: {// 如果是down事件,记录住当前的x坐标            final float x = ev.getX();            if (!mScroller.isFinished()) {                mScroller.abortAnimation();            }            mLastMotionX = x;            break;        }        case MotionEvent.ACTION_MOVE: {            final float x = ev.getX();            final float deltaX = x - mLastMotionX;            if (!mIsBeingDragged) {// 如果还没有处于拖动,则判断两次的差值是否大于最小拖动的距离                if (Math.abs(deltaX) > mTouchSlop) {                    mIsBeingDragged = true;                }            }            if (mIsBeingDragged) {// 如果处于拖动状态,记录住x坐标                mLastMotionX = x;                onMove(deltaX);            }            break;        }        case MotionEvent.ACTION_UP: {            if (mIsBeingDragged) {                final VelocityTracker velocityTracker = mVelocityTracker;                // 先对速度进行一个调整,第一个参数是时间单位,1000毫秒,第二个参数是最大速度。                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);                float velocity = velocityTracker.getXVelocity();// 获取水平方向上的速度                onUp(velocity);            }        }        case MotionEvent.ACTION_CANCEL: {            mIsBeingDragged = false;            if (mVelocityTracker != null) {                mVelocityTracker.recycle();                mVelocityTracker = null;            }            break;        }        }        return true;    }    private void onMove(float x) {        if (mMaxScrollX <= 0) {            if (mViewPager.isFakeDragging() || mViewPager.beginFakeDrag()) {                mViewPager.fakeDragBy(x);            }        } else {            int scrollByX = -(int) (x + 0.5);            if (getScrollX() + scrollByX < 0) {                scrollByX = 0 - getScrollX();                mLeftEdge.onPull(Math.abs(x) / getWidth());            }            if (getScrollX() + scrollByX > mMaxScrollX) {                scrollByX = mMaxScrollX - getScrollX();                mRightEdge.onPull(Math.abs(x) / getWidth());            }            scrollBy(scrollByX, 0);            ViewCompat.postInvalidateOnAnimation(this);        }    }    private void onUp(float velocity) {        if (mMaxScrollX <= 0) {            if (mViewPager.isFakeDragging())                mViewPager.endFakeDrag();        } else {            if (Math.abs(velocity) <= mMinimumVelocity) {                return;            }            mScroller.fling(getScrollX(), 0, -(int) (velocity + 0.5), 0, 0,                    mMaxScrollX, 0, 0, 270, 0);            ViewCompat.postInvalidateOnAnimation(this);        }    }    @Override    public void computeScroll() {        if (mScroller.computeScrollOffset()) {            int oldX = mLastScrollX;            mLastScrollX = mScroller.getCurrX();            if (mLastScrollX < 0 && oldX >= 0) {                mLeftEdge.onAbsorb((int) mScroller.getCurrVelocity());            } else if (mLastScrollX > mMaxScrollX && oldX <= mMaxScrollX) {                mRightEdge.onAbsorb((int) mScroller.getCurrVelocity());            }            int x = mLastScrollX;            if (mLastScrollX < 0) {                x = 0;            } else if (mLastScrollX > mMaxScrollX) {                x = mMaxScrollX;            }            scrollTo(x, 0);        }        ViewCompat.postInvalidateOnAnimation(this);    }    /** 检测mIndicatorOffset的合法性,并计算出其他有关tab的属性值 */    private void checkAndcalculate() {        // 如果指示器起始位置比第一个tab的起始位置还要小,纠正为第一个tab的起始位置,指示器宽度就是第一个tab的宽度        final View firstTab = getChildAt(0);        if (mIndicatorLeft < firstTab.getLeft()) {            mIndicatorLeft = firstTab.getLeft();            mIndicatorWidth = firstTab.getWidth();        }        // 如果指示器起始位置比最后一个tab的起始位置还要大,纠正为最后一个tab的起始位置,指示器宽度就是最后一个tab的宽度        View lastTab = getChildAt(mTabCount - 1);        if (mIndicatorLeft > lastTab.getLeft()) {            mIndicatorLeft = lastTab.getLeft();            mIndicatorWidth = lastTab.getWidth();        }        // 通过指示器的起始位置计算出当前处于第几个position,并且计算出已经偏移了多少,偏移量是以当前所处的tab的宽度的百分比        for (int i = 0; i < mTabCount; i++) {            View tab = getChildAt(i);            if (mIndicatorLeft < tab.getLeft()) {                mCurrentPosition = i - 1;                View currentTab = getChildAt(mCurrentPosition);                mCurrentOffsetPixels = (mIndicatorLeft - currentTab.getLeft())                        / (currentTab.getWidth() + 0.0f);                break;            }        }    }    /** 滚动到指定的child */    public void scrollSelf(int position, float offset) {        if (position >= mTabCount) {            return;        }        final View tab = getChildAt(position);        mIndicatorLeft = (int) (tab.getLeft() + tab.getWidth() * offset + 0.5);        int rightPosition = position + 1;        if (offset > 0 && rightPosition < mTabCount) {            View rightTab = getChildAt(rightPosition);            mIndicatorWidth = (int) (tab.getWidth() * (1 - offset)                    + rightTab.getWidth() * offset + 0.5);        } else {            mIndicatorWidth = tab.getWidth();        }        checkAndcalculate();        int newScrollX = position * mSplitScrollX                + (int) (offset * mSplitScrollX + 0.5);        if (newScrollX < 0) {            newScrollX = 0;        }        if (newScrollX > mMaxScrollX) {            newScrollX = mMaxScrollX;        }        // scrollTo(newScrollX, 0);//滑动        int duration = 100;        if (mSelectedPosition != -1) {            duration = (Math.abs(mSelectedPosition - position)) * 100;        }        mScroller.startScroll(getScrollX(), 0, (newScrollX - getScrollX()), 0,                duration);        ViewCompat.postInvalidateOnAnimation(this);    }    /** 选中指定位置的Tab */    private void selectTab(int position) {        for (int i = 0; i < mTabCount; i++) {            View tab = getChildAt(i);            if (tab != null) {                tab.setSelected(position == i);            }        }    }    /**     * ViewPager的OnPageChangeListener实现类,因为我们需要在PagerTab中获取PagerView的监听,     * 以便可以调整tab     */    private class PageListener implements OnPageChangeListener {        @Override        public void onPageScrolled(int position, float positionOffset,                final int positionOffsetPixels) {            // 根据VierPager的偏移值来滚动tab            scrollSelf(position, positionOffset);            if (mDelegatePageListener != null) {// 这个是提供给外部的                mDelegatePageListener.onPageScrolled(position, positionOffset,                        positionOffsetPixels);            }        }        @Override        public void onPageScrollStateChanged(int state) {            if (state == ViewPager.SCROLL_STATE_IDLE) {                mSelectedPosition = -1;            }            if (mDelegatePageListener != null) {                mDelegatePageListener.onPageScrollStateChanged(state);            }        }        @Override        public void onPageSelected(int position) {            System.out.println("onPageSelected:" + position);            mSelectedPosition = position;            selectTab(position);            if (mDelegatePageListener != null) {                mDelegatePageListener.onPageSelected(position);            }        }    }    /** 如果指示器希望是图片,则继承该接口 */    public interface IconTabProvider {        public int getPageIconResId(int position);        public int getPageSelectedIconResId();    }}

更多相关文章

  1. Android(安卓)一张图理解getWidth和getMeasuredWidth
  2. Android(安卓)可拖拽层叠式卡片列表——WeakView系列
  3. ViewFlipper动态加载View
  4. Android中元素按比例布局
  5. Android开发学习之View测量的内置常用方法
  6. Android根据屏幕宽度,按比例缩放图片
  7. android拖动imageview实现复制效果
  8. Android标题栏渐变色,沉寂式状态栏
  9. android studio向activity_main.xml中拖控件不成功

随机推荐

  1. Android学习——TextView 设置中划线 下
  2. zxing QRcode
  3. android UI
  4. Android指纹解锁边界性问题
  5. sql 游标的使用—游标FOR循环小例子
  6. 基于SQL Server中char,nchar,varchar,nva
  7. 安装sqlserver2000时出现wowexec.exe无反
  8. 基于B-树和B+树的使用:数据搜索和数据库索
  9. 查询表中某字段有重复记录个数的方法
  10. 分享:在存储过程中使用另一个存储过程返回