都知道QQ有一个比较牛逼的效果就是测拉删除效果,目前这个功能,网上自定义控件也有很多实现方式了,本篇也自己实现一个测拉删除效果的自定义控件。虽然功能一样,实现方式不同罢了,也希望提供一些思路,对自己和读者有些帮助~

由于QQ测拉功能强大,手写文字耗费时间,就做个低配置版的测拉效果。废话不多讲,还是乖乖搞事情吧~

1、实现测拉删除的真整体布局:

对于自定义View的布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.itydl.a07sweepview.MainActivity">

    <com.itydl.a07sweepview.SweepView
        android:id="@+id/sv"
        android:layout_width="match_parent"
        android:layout_height="65dp"
        >

        <!--左侧内容区域-->
        <include layout="@layout/content"/>

        <!--右侧删除区域-->
        <include layout="@layout/delete"/>

    </com.itydl.a07sweepview.SweepView>
</RelativeLayout>

通过include的方式引入布局。这两个布局分别表示内容区域,和左侧删除区域。代码如下:

content.xml:

<?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="80dp">
    <TextView
        android:gravity="center"
        android:textColor="#ffffff"
        android:background="#88000000"
        android:textSize="25sp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="测试数据"/>
</LinearLayout>

delete.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="150dp"
              android:layout_height="65dp">

    <TextView
        android:gravity="center"
        android:textColor="#ffffff"
        android:background="#ff0000"
        android:textSize="25sp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="删除"/>

</LinearLayout>

自定义View的代码:

public class SweepView extends ViewGroup {private View mContentView;
    private View mDeleteView;
    private int mDeleteWidth;

    public SweepView(Context context) {this(context, null);
    }public SweepView(Context context, AttributeSet attrs) {super(context, attrs);
    }//xml文件加载完成
    @Override
    protected void onFinishInflate() {//一般用于拿到孩子对象
        mContentView = getChildAt(0);

        mDeleteView = getChildAt(1);

        //拿到DeleteView的params对象
        LayoutParams params = mDeleteView.getLayoutParams();
        mDeleteWidth = params.width;

    }@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//测量孩子
        mContentView.measure(widthMeasureSpec, heightMeasureSpec);

        int measureSpecWidth = MeasureSpec.makeMeasureSpec(mDeleteWidth, MeasureSpec.EXACTLY);
        mDeleteView.measure(measureSpecWidth, heightMeasureSpec);

        int widthMeasureSpecSelf = MeasureSpec.getSize(widthMeasureSpec);
        int heightMeasureSpecSelf = MeasureSpec.getSize(heightMeasureSpec);

        //设置自定义View的大小
        setMeasuredDimension(widthMeasureSpecSelf, heightMeasureSpecSelf);
    }@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {//给孩子布局

        int contentWidth = mContentView.getMeasuredWidth();
        int contentHeight = mContentView.getMeasuredHeight();
        mContentView.layout(0, 0, contentWidth, contentHeight);

        int deleteWidth = mDeleteView.getMeasuredWidth();
        int deleteHeight = mDeleteView.getMeasuredWidth();
        mDeleteView.layout(contentWidth, 0, contentWidth + deleteWidth, deleteHeight);
    }
}

上面进行layout和measue相信已经简单到跟写button代码一样easy了,没啥好说的。

运行程序:

知识简单的布局,点击并没办法滑动。接下来实现滑动效果:

这里滑动采用v4包里面的工具类:ViewDragHelper

2、ViewDragHelper在本项目中的使用:

1)、创建实例

public SweepView(Context context, AttributeSet attrs) {super(context, attrs);
    mDragHelper = ViewDragHelper.create(this,new MyCallBack());
}

2、touch事件委托给 ViewDragHelper离开监听处理,在它内部把触摸事件封装的很好了。

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

3、实现自己的callback:

我们在实例化ViewDragHelper的时候, 这里参数1就代表自定义的View,传入this即可。着重说一下callBack,我们通过创建内部类方式,创建MyCallBack类,并重写里面的回调方法:

class MyCallBack extends ViewDragHelper.Callback{//是否分析(返回true才会有效)view的touch;参数1:触摸的view;2:touch的id。
    @Override
    public boolean tryCaptureView(View child, int pointerId) {// 去分析child。表示我分析ContentView和DeleteView的topuch事件
        System.out.println(child == mContentView);
        return child == mContentView || child == mDeleteView;
    }@Override
    public int clampViewPositionHorizontal(View child, int left, int dx) {System.out.println(left);
        return left;
    }@Override
    public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {}@Override
    public void onViewReleased(View releasedChild, float xvel, float yvel) {}
}

这里一共重写了四个回调方法。我们一一解释都代表什么意思,以及每个方法功能和参数意义。

1)、tryCaptureView(View child, int pointerId)在发生touch事件的down事件的时候回调

代表我是否分析(返回true才会有效)view的touch事件;参数1:当前触摸的view;2:touch的id。如果这个方法返回值为false,表示已不对触摸的view进行分析。则表示我ViewDragHelper不支持滑动效果了。后期的方法也都无效。

在里面打印了一行log,我们手滑内容区域,发现此时已经能够滑动了:

首先,可以实现滑动效果,再此时看log日志:

02-05 08:36:02.445 4596-4596/com.itydl.a07sweepview I/System.out: true
发现返回值为true、因此后续的操作才得以实现。

2)clampViewPositionHorizontal(View child, int left, int dx):水平移动的回调,发生touch时间move时回调。

当touch移动后的回调  参数1:分析的是哪个孩子组件移动了;参数2:左上角的坐标,child的左侧的边距,控件移动到左边什么位置,值会根据移动变化;参数3:增量的x(记录相对上一次的变化量dx>0右滑,dx<0左滑)。这里的值是预期的值,可以在这里做边距的监测,做越界处理。该方法的返回值表示:// 确定要移动多少,移动到什么位置去  return left;。【这个方法里面经常换主角,touch到哪个view这里的child就是哪个view】

这么多理论知识,估计一时明白也够呛,别着急,相信往下继续学习会非常清楚的。

在这里我也做了一行打印:

02-05 08:46:59.090 4596-4596/com.itydl.a07sweepview I/System.out: 34-----34
02-05 08:46:59.107 4596-4596/com.itydl.a07sweepview I/System.out: 66-----32
02-05 08:46:59.125 4596-4596/com.itydl.a07sweepview I/System.out: 99-----33
02-05 08:46:59.142 4596-4596/com.itydl.a07sweepview I/System.out: 146-----47
02-05 08:46:59.159 4596-4596/com.itydl.a07sweepview I/System.out: 177-----31
02-05 08:46:59.175 4596-4596/com.itydl.a07sweepview I/System.out: 203-----26
02-05 08:46:59.200 4596-4596/com.itydl.a07sweepview I/System.out: 227-----24

通过log可以更加清晰的了解参数的具体意义,那个dx值是一个变化量。必须我最初left=0,下一次=10,第三次=60.那么dx分别为:10,50

除了clampViewPositionHorizontal当然还有clampViewPositionHorizontal,会一种相信另一种也是信手拈来。

3)、onViewPositionChanged(View changedView, int left, int top, int dx, int dy)当【控件位置】移动时的回调

参数意义:// @changedView: 哪个view移动了      // @left,top:view移动后的左上角的坐标   // @dx,dy: 移动的增量

这个方法跟上边clampViewPositionHorizontal差不多,都是移动回调,如果说clampViewPositionHorizontal用于处理越界以及确定位置的话,那么onViewPositionChanged一般用于移动view的布局重置。后续代码可以看到两者的各自责任,以及实现什么功能。

4)、onViewReleased(View releasedChild, float xvel, float yvel)松开收时候的回调up事件的回调。

@releasedChild:松开了哪个view; @xvel,yvel:速率。该方法一般用于“松手回弹”效果的操作,即松开手,自定义控件往哪个位置回弹。上一篇自定义ViewPage可以看到回弹效果,那里是使用Scrollor实现的,而在ViewDragHelper有它的手段,稍后会用到。

3、滑动删除滑动的实现。

了解了上述几个方法,我们就要在这几个方法里面做一些操作了,比如先实现滑动效果。不仅仅点击contentview区域可滑动,点击deleteview区域也可以实现控件的整体滑动效果。

代码如下:

@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {int contentWidth = mContentView.getMeasuredWidth();
    int contentHeight = mContentView.getMeasuredHeight();

    int deleteWidth = mDeleteView.getMeasuredWidth();
    int deleteHeight = mDeleteView.getMeasuredWidth();

    if (changedView == mContentView) {mDeleteView.layout(contentWidth + left, 0, contentWidth + deleteWidth + left, deleteHeight);
    } else if (changedView == mDeleteView) {mContentView.layout(left-contentWidth,0,left,contentHeight);
    }
}

就像前面介绍所说的在onViewPositionChanged方法中根据move事件,对xml可以做重新布局操作。上面代码的值都是一些很简单的小算法,相信看起来还是蛮简单的。当我们滑动灰色内容区域,此时的changeView就是灰色内容区域,当滑动删除位置,此时的changeView就代表了红色区域。

4、滑动删除边界的处理,解决越界问题。

@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {Log.e("YDL", dx + "");
    if (child == mContentView) {if (left < 0 && left < -mDeleteView.getMeasuredWidth()) {//左滑
            return -mDeleteView.getMeasuredWidth();
        } else if (left > 0) {//右滑
            return 0;
        }} else if (child == mDeleteView) {if (left > mContentView.getMeasuredWidth()) {return mContentView.getMeasuredWidth();
        }else if(left < mContentView.getMeasuredWidth() - mDeleteView.getMeasuredWidth()){return mContentView.getMeasuredWidth() - mDeleteView.getMeasuredWidth();
        }}return left;
}

就像前面介绍所说的在clampViewPositionHorizontal方法中根据move事件,根据滑动不同的子View,来确定边界值不越界。运行程序:

此时,实现了滑动效果,并解决了越界问题。

5、实现滑动“回弹”效果

此时核心的逻辑已经实现了,接下来就是处理一些细节

@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {// up时的回调
    // @releasedChild:松开了哪个view
    // @xvel,yvel:速率
    int left = mContentView.getLeft();

    int contentWidth = mContentView.getMeasuredWidth();
    int contentHeight = mContentView.getMeasuredHeight();

    int deleteWidth = mDeleteView.getMeasuredWidth();
    int deleteHeight = mDeleteView.getMeasuredWidth();

    if(-left < mDeleteView.getMeasuredWidth()/2){mContentView.layout(0, 0, contentWidth, contentHeight);

        mDeleteView.layout(contentWidth, 0, contentWidth + deleteWidth, deleteHeight);
    }else{mContentView.layout(-deleteWidth, 0, contentWidth - deleteWidth, contentHeight);

        mDeleteView.layout(contentWidth - deleteWidth, 0, contentWidth, deleteHeight);
    }}

UP事件后,通过判断ContentView左侧坐标位置,重新确定了两个孩子组件的位置。运行科自行调试,恢复布局很生硬,因此加入缓慢恢复功能。

修改上述代码:

@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {// up时的回调
    // @releasedChild:松开了哪个view
    // @xvel,yvel:速率
    int left = mContentView.getLeft();

    int contentWidth = mContentView.getMeasuredWidth();

    int deleteWidth = mDeleteView.getMeasuredWidth();

     if(-left < mDeleteView.getMeasuredWidth()/2){mDragHelper.smoothSlideViewTo(mContentView,0,0);

        mDragHelper.smoothSlideViewTo(mDeleteView,contentWidth,0);
    }else{mDragHelper.smoothSlideViewTo(mContentView,-deleteWidth,0);

        mDragHelper.smoothSlideViewTo(mDeleteView,contentWidth - deleteWidth,0);
    }//效果等同于invalidate()---->会调用computeScroll
    ViewCompat.postInvalidateOnAnimation(SweepView.this);
}

其中smoothSlideViewTo已经把平滑恢复封装的很好了。只需要传入View、该最终左侧坐标、最终top坐标即可。

这里必须进行invalidate();刷新,使用了ViewCompat.postInvalidateOnAnimation(SweepView.this);代替,这个api可以兼容更低的版本。然而这里只是委托作用,真正的平滑移动效果在移动回调方法computeScroll()中。重写之:

@Override
public void computeScroll() {if(mDragHelper.continueSettling(true)){//直接刷新即可
        ViewCompat.postInvalidateOnAnimation(SweepView.this);
    }
}

在这里面,只需要简单刷新界面调用 ViewCompat.postInvalidateOnAnimation(SweepView.this);即可。当我们调用 ViewCompat.postInvalidateOnAnimation(SweepView.this);后,内部会调用 computeScroll方法,在这里面隔一段距离并借助scrollTo()完成平滑移动。运行:

6、滑动删除的实现:

在MainActivity中使用ListView,相信都会熟练使用。在定义Adapter适配器的时候,我们通过下面方式实现ListView加载数据,而且每个Item都加入是用自定义控件。

@Override
public View getView(int position, View convertView, ViewGroup parent) {ViewHolder holder;
    if (convertView == null) {holder = new ViewHolder();
        convertView = View.inflate(MainActivity.this, R.layout.item_list, null);

        holder.mTextView = (TextView) convertView.findViewById(R.id.tv_content);
        holder.mSweepView = (SweepView) convertView.findViewById(R.id.sv);

        convertView.setTag(holder);
    } else {holder = (ViewHolder) convertView.getTag();
    }String itemStr = (String) getItem(position);

    holder.mTextView.setText(itemStr);

    return convertView;
}

getView的item的布局:

<?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.itydl.a07sweepview.SweepView
        android:id="@+id/sv"
        android:layout_width="match_parent"
        android:layout_height="65dp"
        >

        <!--左侧内容区域-->
        <include layout="@layout/content"/>

        <!--右侧删除区域-->
        <include layout="@layout/delete"/>

    </com.itydl.a07sweepview.SweepView>
</LinearLayout>

运行程序:

7、实现真正的侧栏删除,以及一些细节的处理。

首先添加可删除事件,直接在getView方法里面设置item上的孩子组件的点击事件即可。

//给listview的Item上添加点击事件,点击删除该item条目
holder.mTextDelet.setOnClickListener(new View.OnClickListener() {@Override
    public void onClick(View v) {mList.remove(position);

        notifyDataSetChanged();
    }
});

运行程序:

此时已经可以完成删除了,只不过我们发现删除之后接着看下一条item不对劲,我没有滑动下一条item,怎么这么显示呢?还有我们往下滑动ListView,由于复用的原因,也会出现类似情况。

那么紧跟着解决细节问题:

要解决问题其实也挺简单,只需要再点击item的时候,关闭掉所有打开的item即可。那么,如何才能控制打开与关闭呢?其实在前面的恢复布局就已经隐含了这个功能。只需要把原来位置抽取一个方法就好了。

@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {// up时的回调
    // @releasedChild:松开了哪个view
    // @xvel,yvel:速率
    int left = mContentView.getLeft();

    int contentWidth = mContentView.getMeasuredWidth();

    int deleteWidth = mDeleteView.getMeasuredWidth();

     if(-left < mDeleteView.getMeasuredWidth()/2){mDragHelper.smoothSlideViewTo(mContentView,0,0);

        mDragHelper.smoothSlideViewTo(mDeleteView,contentWidth,0);
    }else{mDragHelper.smoothSlideViewTo(mContentView,-deleteWidth,0);

        mDragHelper.smoothSlideViewTo(mDeleteView,contentWidth - deleteWidth,0);
    }//效果等同于invalidate()---->会调用computeScroll
    ViewCompat.postInvalidateOnAnimation(SweepView.this);
}

修改为:

@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {// up时的回调
    // @releasedChild:松开了哪个view
    // @xvel,yvel:速率
    int left = mContentView.getLeft();

     if(-left < mDeleteView.getMeasuredWidth()/2){//还原item(关闭)
         close();
     }else{//(打开)
         open();
     }
}

打开和关闭方法就要暴露方法出去:

/**
 * 关闭item
 */
public void close() {int contentWidth = mContentView.getMeasuredWidth();

    if(mSweepChangeListener != null){mSweepChangeListener.sweepChanged(SweepView.this,false);
    }mDragHelper.smoothSlideViewTo(mContentView,0,0);

    mDragHelper.smoothSlideViewTo(mDeleteView,contentWidth,0);

    //效果等同于invalidate()---->会调用computeScroll.这里必须有刷新才可以
    ViewCompat.postInvalidateOnAnimation(SweepView.this);
}/**
 * 打开item
 */
public void open() {int contentWidth = mContentView.getMeasuredWidth();

    int deleteWidth = mDeleteView.getMeasuredWidth();

    if(mSweepChangeListener != null){mSweepChangeListener.sweepChanged(SweepView.this,true);
    }mDragHelper.smoothSlideViewTo(mContentView,-deleteWidth,0);

    mDragHelper.smoothSlideViewTo(mDeleteView,contentWidth - deleteWidth,0);

    //效果等同于invalidate()---->会调用computeScroll.这里必须有刷新才可以
    ViewCompat.postInvalidateOnAnimation(SweepView.this);
}

为了判断item的view是打开还是关闭,因此在自定义View中暴露接口出去,并在每一次getView的时候通过接口会掉的方式告知当前的item是打开还是关闭的:

public void setOnSweepChangeListener(OnSweepChangeListener listener){this.mSweepChangeListener = listener;
}public interface OnSweepChangeListener{/**
     * item是自定义SweepView对象。每个ListView的item都是SweepView对象,且不同
     * @param sweepView
     * @param isOpend
     */
    void sweepChanged(SweepView sweepView,boolean isOpend);
}

对于接口对象调用接口方法,上边的打开和关闭方法中已经很给出了调用。

最后是在getView方法中作如下修改:

holder.mSweepView.setOnSweepChangeListener(new SweepView.OnSweepChangeListener() {@Override
    public void sweepChanged(SweepView sweepView, boolean isOpend) {if(isOpend){//如果打开,将该item对应的SweepView对象则保存至集合中
            if(!mSweepViews.contains(sweepView)){mSweepViews.add(sweepView);
            }}else{mSweepViews.remove(sweepView);
        }}
});

当加载每一个 item的时候,每条item都监听当前Item的SweepView子布局是打开还是关闭, 如果打开,将该item对应的SweepView对象则保存至集合中。  而当点击了删除按钮的时候,我们需要如下操作:

//给listview的Item上添加点击事件,点击删除该item条目
holder.mTextDelet.setOnClickListener(new View.OnClickListener() {@Override
    public void onClick(View v) {mList.remove(itemStr);

        closeAll();

        notifyDataSetChanged();
    }
});
private void closeAll() {//使用迭代器
        /*ListIterator<SweepView> iterator = mSweepViews.listIterator();
        while (iterator.hasNext()) {
            SweepView view = iterator.next();
            view.close();
        }*/
    for (SweepView sweepView : mSweepViews) {sweepView.close();
    }
}

此时实现了删除,运行程序:

可能你觉得已经完成了,其实,还存在一个严重的bug,当我们往下滑动的时候就可以看到了,以及我们滑动多个item都能打开,显然不符合QQ滑动删除效果。那么最后就解决这个bug:

解决思路:当我们按下的时候,记录按下滑动打开时候的SweepView的实例,这个实例用临时变量保存起来。然后通过接口回调的方式,传递用户下一次按下的item的SweepView实例。这次新添加的接口回调方法,在View的onTouchEvent的Action_Down的时候调用。代码如下:

1)添加接口方法

/**
 * 按下时候的回调。按下,如果按下时当前的item与打开的item不一致,按下关闭掉item
 * @param sweepView
 */
void sweepDown(SweepView sweepView);

2)在 View的onTouchEvent的Action_Down的时候调用。

@Override
public boolean onTouchEvent(MotionEvent event) {if(event.getAction() == MotionEvent.ACTION_DOWN){//按下的时候
        mSweepChangeListener.sweepDown(this);
    }mDragHelper.processTouchEvent(event);
    return true;
}

3)、 记录按下滑动打开时候的SweepView的实例,以及实现只允许一条item展示。

private SweepView mSweepView;
holder.mSweepView.setOnSweepChangeListener(new SweepView.OnSweepChangeListener() {@Override
    public void sweepChanged(SweepView sweepView, boolean isOpend) {if (isOpend) {mSweepView = sweepView;

            //如果打开,将该item对应的SweepView对象则保存至集合中
            if (!mSweepViews.contains(sweepView)) {mSweepViews.add(sweepView);
            }} else {mSweepViews.remove(sweepView);
        }}@Override
    public void sweepDown(SweepView sweepView) {//如果不滑动,mSweepView为null,因此要过滤为null情况
        if(mSweepView != null && mSweepView != sweepView){mSweepView.close();
        }}
});

最后运行看看地方QQ侧拉删除的效果吧:

效果还不错,加个关注呗~

打开微信扫描下方二维码查看更多安卓文章:

打开微信搜索公众号    Android程序员开发指南   或者手机扫描下方二维码 在公众号Android文章。

微信公众号图片:

自定义View 篇四《低仿QQ测拉删除》相关推荐

  1. 自定义 View(一)仿 QQ 列表 Item 侧拉删除功能

    博主声明: 转载请在开头附加本文链接及作者信息,并标记为转载.本文由博主 威威喵 原创,请多支持与指教. 本文首发于此   博主:威威喵  |  博客主页:https://blog.csdn.net/ ...

  2. 【Android自定义View实战】之仿QQ运动步数圆弧及动画,Dylan计步中的控件StepArcView

    转载请注明出处:http://blog.csdn.net/linglongxin24/article/details/52936609 [DylanAndroid的csdn博客] 在之前的Androi ...

  3. android自定义起止时间的时间刻度尺,Android 自定义View篇(六)实现时钟表盘效果...

    前言 Android 自定义 View 是高级进阶不可或缺的内容,日常工作中,经常会遇到产品.UI 设计出花里胡哨的界面.当系统自带的控件不能满足开发需求时,就只能自己动手撸一个效果. 本文就带自定义 ...

  4. 自定义Dialog提示框高仿QQ浏览器版本更新提示框

    前言: 今天是5月7号,真的好久没有写博客了,时光匆匆,我总感觉自己忙忙碌碌似的,静想片刻确实是挺忙的,但是在繁忙当中却缺少了总结归纳,相信大家都知道总结归纳的重要性了,今天我要和大家分享我的自定义D ...

  5. android canvas_Android 自定义View篇(七)实现环形进度条效果

    前言 Android 自定义 View 是高级进阶不可或缺的内容,日常工作中,经常会遇到产品.UI 设计出花里胡哨的界面.当系统自带的控件不能满足开发需求时,就只能自己动手撸一个效果. 本文就带自定义 ...

  6. 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发机制...

    前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...

  7. 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发逻辑及经常遇到的一些“诡异”现象

    前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...

  8. Android 常见界面控件(ListView、RecyclerView、自定义View篇)

    Android 常见界面控件(ListView.RecyclerView.自定义View篇) 目录 3.3 ListView的使用 3.3.1 ListView控件的简单使用 3.3.2 常用数据适配 ...

  9. 金融类自定义View(四)--股票蜡烛图以及MA、BOLL指标

    *本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 金融类自定义View(四)–股票蜡烛图(包含MA.BOLL指标)以及代码重构 前言 本文只描述蜡烛图单独的绘制逻辑,至于和分时图相 ...

最新文章

  1. android listview 自动循环滚动条,ListView的自动循环滚动显示【原创】
  2. MyBatis在insert插入操作时返回主键ID的配置
  3. 百练2811:熄灯问题
  4. 在Eclipse中编写servlet时出现The import javax.servlet cannot be resolved 问题解决办法
  5. [html] 说说如果a链接href=““(空)时点击时会有什么表现?
  6. idea下拉项目_推荐几款非常好用的IDEA插件(香)
  7. 想要一本Linux书籍?投出您想要的Linux书籍,由红联论坛邮寄给您(2007新增)(转)...
  8. Java 集合系列12之 Hashtable详细介绍(源码解析)和使用示例
  9. 评分卡建模工具scorecardpy全解读
  10. k8s的job和CronJob
  11. Ubuntu22.04安装Arduino IDE及Arduino UNO(使用CH341驱动)调试方法
  12. 广度优先搜索算法1-已知若干个城市的路线,求从一个城市到另外一个城市的路径,要求路径中经过的城市最少。
  13. vue开发h5 公众号
  14. 获取当前时间的东八区时间
  15. 一文玩转 WebDriver API
  16. Android5g手机,Android 11为5种不同状态的5G手机做准备
  17. 25匹马,5个赛道,决出前n名
  18. 合成孔径雷达图像去噪算法研究综述
  19. 史上最简单MYSQL教程详解(基础篇)之初识MySQL数据库以及环境配置
  20. 2020国赛A题 炉温曲线

热门文章

  1. 什么?你对新媒体运营感兴趣?来听听一年新媒体人的故事。
  2. Android单独编译驱动模块
  3. 先进先出的数据冲销处理(转邹健的个人空间)
  4. 人工智能训练师:用数据“喂养”AI,教它们“更懂”人类
  5. 参数估计-矩估计和极大似然估计概述
  6. pandas 0.23.4 :'pd.ewma'没有这个模块,改用`Series.ewm` 或 降低版本到 pandas 0.21.0
  7. 计算机网络:ICMP协议
  8. 我爷爷吸烟,我爸爸也吸烟,轮到我不能断了香火
  9. Java程序员必会!西安java培训机构哪个靠谱
  10. 时间戳格式化一定要 乘1000呀!!!!