版权声明:本文为博主原创文章。未经博主同意不得转载。 https://blog.csdn.net/singwhatiwanna/article/details/25546871

转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/25546871(来自singwhatiwanna的csdn博客)

前言

Android中,大家都用过ListView,ExpandableListView等,或许你还用过PinnedHeaderListView,可是假设我说PinnedHeaderExpandableListView,你听过吗?还有可下拉的PinnedHeaderExpandableListView呢?没听过也不要紧,本文就是介绍这个东西的,为了让大家有更直观的了解。先上效果图。通过效果图能够看出,首先它是一个ExpandableListView,可是它的头部能够固定,其次。在它的上面另一个头部能够来回伸缩。恩,这就是本文要介绍的自己定义view。

为了提高复用性。这个效果我分成来了2个view来实现,第一个是PinnedHeaderExpandableListView来实现头部固定的ExpandableListView。第二个view是StickyLayout,这个view具有一个能够上下滑动的头部,最后将这2个view组合在一起,就达到了例如以下的效果。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2luZ3doYXRpd2FubmE=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />

PinnedHeaderExpandableListView的实现

关于ExpandableListView的用法请自己了解下,网上非常多。

关于这个view,它的实现方式是这种:

首先继承自ExpandableListView,然后再它滚动的时候我们要监听顶部的item是属于哪个group的,当知道是哪个group以后,我们就在view的顶部绘制这个group。这样就完毕了头部固定这个效果。当然过程远没有我描写叙述的这个简单,期间有一些问题须要正确处理。以下分别说明:

1.怎样知道顶部的item是哪个group,这个简单。略过;

 

2. 怎样在顶部绘制group,这个我们能够重写dispatchDraw这种方法。在这种方法里drawChild就可以。dispatchDraw是被draw方法用来绘制子元素的,和onDraw不同。onDraw是用来绘制自己的。我们要知道。view画图的过程是先背景再自己最后在绘制子元素;

 

3. 滑动过程中header的更新,当滑动的时候。要去推断最上面的group是否发生改变,假设改变了就须要又一次绘制group,这个非常easy。注意到有一个效果,就是当两个group接近的时候,以下的group会把上面的header推上去,这个效果就难处理一些,推动的效果能够用layout来实现,通过layout将上面的group的位置给改变就能够了;

 

4.header的点击,要知道固定的头部是绘制上去的,而且它也不是ExpandableListView的子元素。能够理解为我们凭空绘制的一个view,假设处理它的点击,这个貌似非常难。可是能够这么解决,当点击事件发生的时候,推断其区域是否落在header内部,假设落在了内部将能够处理点击事件了,处理后要讲事件消耗掉。

 

同一时候。我还提供了一个接口,OnHeaderUpdateListener,通过实现这个接口,PinnedHeaderExpandableListView就知道怎样绘制和更新header了。以下看代码:

/**
The MIT License (MIT) Copyright (c) 2014 singwhatiwanna
https://github.com/singwhatiwanna
http://blog.csdn.net/singwhatiwanna Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/ package com.ryg.expandable.ui; import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AbsListView;
import android.widget.ExpandableListView;
import android.widget.AbsListView.OnScrollListener; public class PinnedHeaderExpandableListView extends ExpandableListView implements OnScrollListener {
private static final String TAG = "PinnedHeaderExpandableListView"; public interface OnHeaderUpdateListener {
/**
* 採用单例模式返回同一个view对象就可以
* 注意:view必须要有LayoutParams
*/
public View getPinnedHeader(); public void updatePinnedHeader(int firstVisibleGroupPos);
} private View mHeaderView;
private int mHeaderWidth;
private int mHeaderHeight; private OnScrollListener mScrollListener;
private OnHeaderUpdateListener mHeaderUpdateListener; private boolean mActionDownHappened = false; public PinnedHeaderExpandableListView(Context context) {
super(context);
initView();
} public PinnedHeaderExpandableListView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
} public PinnedHeaderExpandableListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
} private void initView() {
setFadingEdgeLength(0);
setOnScrollListener(this);
} @Override
public void setOnScrollListener(OnScrollListener l) {
if (l != this) {
mScrollListener = l;
}
super.setOnScrollListener(this);
} public void setOnHeaderUpdateListener(OnHeaderUpdateListener listener) {
mHeaderUpdateListener = listener;
if (listener == null) {
return;
}
mHeaderView = listener.getPinnedHeader();
int firstVisiblePos = getFirstVisiblePosition();
int firstVisibleGroupPos = getPackedPositionGroup(getExpandableListPosition(firstVisiblePos));
listener.updatePinnedHeader(firstVisibleGroupPos);
requestLayout();
postInvalidate();
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mHeaderView == null) {
return;
}
measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec);
mHeaderWidth = mHeaderView.getMeasuredWidth();
mHeaderHeight = mHeaderView.getMeasuredHeight();
} @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (mHeaderView == null) {
return;
}
mHeaderView.layout(0, 0, mHeaderWidth, mHeaderHeight);
} @Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mHeaderView != null) {
drawChild(canvas, mHeaderView, getDrawingTime());
}
} @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
Log.d(TAG, "dispatchTouchEvent");
int pos = pointToPosition(x, y);
if (y >= mHeaderView.getTop() && y <= mHeaderView.getBottom()) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mActionDownHappened = true;
} else if (ev.getAction() == MotionEvent.ACTION_UP) {
int groupPosition = getPackedPositionGroup(getExpandableListPosition(pos));
if (groupPosition != INVALID_POSITION && mActionDownHappened) {
if (isGroupExpanded(groupPosition)) {
collapseGroup(groupPosition);
} else {
expandGroup(groupPosition);
}
mActionDownHappened = false;
} }
return true;
} return super.dispatchTouchEvent(ev);
} protected void refreshHeader() {
if (mHeaderView == null) {
return;
}
int firstVisiblePos = getFirstVisiblePosition();
int pos = firstVisiblePos + 1;
int firstVisibleGroupPos = getPackedPositionGroup(getExpandableListPosition(firstVisiblePos));
int group = getPackedPositionGroup(getExpandableListPosition(pos)); if (group == firstVisibleGroupPos + 1) {
View view = getChildAt(1);
if (view.getTop() <= mHeaderHeight) {
int delta = mHeaderHeight - view.getTop();
mHeaderView.layout(0, -delta, mHeaderWidth, mHeaderHeight - delta);
}
} else {
mHeaderView.layout(0, 0, mHeaderWidth, mHeaderHeight);
} if (mHeaderUpdateListener != null) {
mHeaderUpdateListener.updatePinnedHeader(firstVisibleGroupPos);
}
} @Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (mHeaderView != null && scrollState == SCROLL_STATE_IDLE) {
int firstVisiblePos = getFirstVisiblePosition();
if (firstVisiblePos == 0) {
mHeaderView.layout(0, 0, mHeaderWidth, mHeaderHeight);
}
}
if (mScrollListener != null) {
mScrollListener.onScrollStateChanged(view, scrollState);
}
} @Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (totalItemCount > 0) {
refreshHeader();
}
if (mScrollListener != null) {
mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
} }

下拉效果的实现

如今介绍第二个view,即StickyLayout,字面意思是黏性的layout。这个view内部分为2部分,header和content,而且header能够来回收缩。

至于怎样让header上下收缩。有几个看似可行的方案,我们分析下:

1.通过scrollTo/scrollBy来实现view的滚动,因为这两个api是对view内容的滚动,无论怎么滚动,内容都不会覆盖到别的view上去,除非你用了FrameLayout、RelativeLayout且经过精心布局,否则非常难实现将内容滚动到别的view上面,即便如此。假设将header展开和收缩也是一个非常大的问题,除非你动态地去调整header的布局。通过分析,这种方法不可行;

 

2. 通过动画来实现view的平移,从效果上来说。这个可行的,使用平移和缩放动画并结合手势的监听。能够实现这个效果。可是动画有一个问题。就是点击事件的处理,我们知道view动画。即使view区域发生了改变,可是事件点击区域仍然不变。而属性动画在3.0以下系统上根本不支持,就算採用兼容包。可是属性动画在3.0以下系统的点击事件区域仍然不会随着动画而改变。这更加证实了一个结论:动画是对view的显示发生作用,而不是view这个对象,也即是说动画并不影响view的区域(4个顶点)。

说了这么多,好像还挺晦涩的,直白来说,採用动画来实现的问题是:在3.0以下系统。尽管view已经看起来跑到新位置了。可是你在新位置点击是不会触发点击事件的,而老位置还是能够触发点击事件,这就意味着,content移动后,content无法点击了。基于此。动画不可行;

 

3.第三种方案,也就是本文所採用的方案:通过手势监听结合header高度的改变来实现整个动画效果,详细点就是。当手指滑动的时候,动态去调整header的高度并重绘。这个时候因为header的高度发生了改变。所以content中的内容就会挤上去。就实现了本文中的效果了。

有了这个StickyLayout,想实现相似的效果,这要把能够收缩的内容放到header里。其它内容放到content里就可以。以下看代码:

/**
The MIT License (MIT) Copyright (c) 2014 singwhatiwanna
https://github.com/singwhatiwanna
http://blog.csdn.net/singwhatiwanna Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/ package com.ryg.expandable.ui; import java.util.NoSuchElementException; import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.LinearLayout; public class StickyLayout extends LinearLayout {
private static final String TAG = "StickyLayout"; public interface OnGiveUpTouchEventListener {
public boolean giveUpTouchEvent(MotionEvent event);
} private View mHeader;
private View mContent;
private OnGiveUpTouchEventListener mGiveUpTouchEventListener; // header的高度 单位:px
private int mOriginalHeaderHeight;
private int mHeaderHeight; private int mStatus = STATUS_EXPANDED;
public static final int STATUS_EXPANDED = 1;
public static final int STATUS_COLLAPSED = 2; private int mTouchSlop; // 分别记录上次滑动的坐标
private int mLastX = 0;
private int mLastY = 0; // 分别记录上次滑动的坐标(onInterceptTouchEvent)
private int mLastXIntercept = 0;
private int mLastYIntercept = 0; // 用来控制滑动角度,仅当角度a满足例如以下条件才进行滑动:tan a = deltaX / deltaY > 2
private static final int TAN = 2; public StickyLayout(Context context) {
super(context);
} public StickyLayout(Context context, AttributeSet attrs) {
super(context, attrs);
} @TargetApi(Build.VERSION_CODES.HONEYCOMB)
public StickyLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
} @Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (hasWindowFocus && (mHeader == null || mContent == null)) {
initData();
}
} private void initData() {
int headerId= getResources().getIdentifier("header", "id", getContext().getPackageName());
int contentId = getResources().getIdentifier("content", "id", getContext().getPackageName());
if (headerId != 0 && contentId != 0) {
mHeader = findViewById(headerId);
mContent = findViewById(contentId);
mOriginalHeaderHeight = mHeader.getMeasuredHeight();
mHeaderHeight = mOriginalHeaderHeight;
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
Log.d(TAG, "mTouchSlop = " + mTouchSlop);
} else {
throw new NoSuchElementException("Did your view with \"header\" or \"content\" exist?");
}
} public void setOnGiveUpTouchEventListener(OnGiveUpTouchEventListener l) {
mGiveUpTouchEventListener = l;
} @Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int intercepted = 0;
int x = (int) event.getX();
int y = (int) event.getY(); switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
mLastXIntercept = x;
mLastYIntercept = y;
mLastX = x;
mLastY = y;
intercepted = 0;
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if (mStatus == STATUS_EXPANDED && deltaY <= -mTouchSlop) {
intercepted = 1;
} else if (mGiveUpTouchEventListener != null) {
if (mGiveUpTouchEventListener.giveUpTouchEvent(event) && deltaY >= mTouchSlop) {
intercepted = 1;
}
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = 0;
mLastXIntercept = mLastYIntercept = 0;
break;
}
default:
break;
} Log.d(TAG, "intercepted=" + intercepted);
return intercepted != 0;
} @Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
Log.d(TAG, "x=" + x + " y=" + y + " mlastY=" + mLastY);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
Log.d(TAG, "mHeaderHeight=" + mHeaderHeight + " deltaY=" + deltaY + " mlastY=" + mLastY);
mHeaderHeight += deltaY;
setHeaderHeight(mHeaderHeight);
break;
}
case MotionEvent.ACTION_UP: {
// 这里做了下推断,当松开手的时候。会自己主动向两边滑动。详细向哪边滑,要看当前所处的位置
int destHeight = 0;
if (mHeaderHeight <= mOriginalHeaderHeight * 0.5) {
destHeight = 0;
mStatus = STATUS_COLLAPSED;
} else {
destHeight = mOriginalHeaderHeight;
mStatus = STATUS_EXPANDED;
}
// 慢慢滑向终点
this.smoothSetHeaderHeight(mHeaderHeight, destHeight, 500);
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return true;
} public void smoothSetHeaderHeight(final int from, final int to, long duration) {
final int frameCount = (int) (duration / 1000f * 30) + 1;
final float partation = (to - from) / (float) frameCount;
new Thread("Thread#smoothSetHeaderHeight") { @Override
public void run() {
for (int i = 0; i < frameCount; i++) {
final int height;
if (i == frameCount - 1) {
height = to;
} else {
height = (int) (from + partation * i);
}
post(new Runnable() {
public void run() {
setHeaderHeight(height);
}
});
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}; }.start();
} private void setHeaderHeight(int height) {
Log.d(TAG, "setHeaderHeight height=" + height);
if (height < 0) {
height = 0;
} else if (height > mOriginalHeaderHeight) {
height = mOriginalHeaderHeight;
}
if (mHeaderHeight != height || true) {
mHeaderHeight = height;
mHeader.getLayoutParams().height = mHeaderHeight;
mHeader.requestLayout();
}
} }

关于这个view还须要说明的是滑动冲突,假设content里是个listview,因为两者都能竖向滑动,这就会有冲突。怎样解决滑动冲突一直是一个难点,我的解决思路是这种:首先StickyLayout默认不拦截事件,假设子元素不处理事件。它就会上下滑动。假设子元素处理了事件,它就不会滑动。所以在最外层我们须要知道子元素何时处理事件、何时不处理事件,为了解决问题。提供了一个接口OnGiveUpTouchEventListener,当子元素不处理事件的时候,StickyLayout就能够处理滑动事件。详细请參看代码中的onInterceptTouchEvent和onTouchEvent。以下看一下activity对这2个接口的实现。

Activity的实现

因为Activity中大部分代码都是环绕ExpandableListAdapter,是比較普通的代码,这里要介绍的是activity对上述2个view中接口的实现,分别为PinnedHeaderExpandableListView中怎样绘制和更新固定的头部以及StickyLayout中content何时放弃事件处理。

    @Override
public View getPinnedHeader() {
if (mHeaderView == null) {
mHeaderView = (ViewGroup) getLayoutInflater().inflate(R.layout.group, null);
mHeaderView.setLayoutParams(new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
}
return mHeaderView;
} @Override
public void updatePinnedHeader(int firstVisibleGroupPos) {
Group firstVisibleGroup = (Group) adapter.getGroup(firstVisibleGroupPos);
TextView textView = (TextView) getPinnedHeader().findViewById(R.id.group);
textView.setText(firstVisibleGroup.getTitle());
} @Override
public boolean giveUpTouchEvent(MotionEvent event) {
if (expandableListView.getFirstVisiblePosition() == 0) {
View view = expandableListView.getChildAt(0);
if (view != null && view.getTop() >= 0) {
return true;
}
}
return false;
}

总结

demo效果上还是不错的,在4.x和2.x上都经过測试,完美执行,市面上不少android应用有相似的效果,欢迎大家fork代码。欢迎大家交流。

代码地址

https://github.com/singwhatiwanna/PinnedHeaderExpandableListView

须要注意的是:该项目採用MIT共享协议公布,意味着假设你要使用或改动它,必须在源码中保留头部的版权声明,这个要求够不够低啊,哈哈!

可下拉的PinnedHeaderExpandableListView的实现的更多相关文章

  1. Android SwipeRefreshLayout 下拉刷新——Hi_博客 Android App 开发笔记

    以前写下拉刷新 感觉好费劲,要判断ListView是否滚到顶部,还要加载头布局,还要控制 头布局的状态,等等一大堆.感觉麻烦死了.今天学习了SwipeRefreshLayout 的用法,来分享一下,有 ...

  2. ASP.NET Aries 入门开发教程4:查询区的下拉配置

    背景: 今天去深圳溜达了一天,刚回来,看到首页都是微软大法好,看来离.NET的春天就差3个月了~~ 回到正题,这篇的教程讲解下拉配置. 查询区的下拉配置: 1:查询框怎么配置成下拉? 在配置表头:格式 ...

  3. 带你实现开发者头条APP(五)--RecyclerView下拉刷新上拉加载

    title: 带你实现开发者头条APP(五)--RecyclerView下拉刷新上拉加载 tags: -RecyclerView,下拉刷新,上拉加载更多 grammar_cjkRuby: true - ...

  4. jquery实现下拉框多选

    一.说明 本文是利用EasyUI实现下拉框多选功能,在ComboxTree其原有的基础上对样式进行了改进,样式表已上传demo,代码如下 二.代码 <!DOCTYPE html PUBLIC & ...

  5. iosselect:一个js picker项目,在H5中实现IOS的select下拉框效果

    具体文档和demo可以访问github:https://github.com/zhoushengmufc/iosselect 移动端浏览器对于select的展示样式是不一致的,ios下是类似原生的pi ...

  6. 关于MJRefresh的下拉加载数据bug

    当没有更多数据的时候显示NoMoreData 我的理解是先结束刷新再显示没有更多 今天之前一直没发现有问题 贴之前的代码 [self.collectionView reloadData]; [self ...

  7. listview下拉刷新和上拉加载更多的多种实现方案

    listview经常结合下来刷新和上拉加载更多使用,本文总结了三种常用到的方案分别作出说明. 方案一:添加头布局和脚布局        android系统为listview提供了addfootview ...

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

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

  9. Html5下拉控件同时支持文本输入和下拉代码

    有时候,下拉框不能满足我们的业务需求,还需要同时支持用户输入内容,默认的select标签是不支持用户输入的,下面我说一下原生的select如何支持用户输入,代码如下: <!DOCTYPE htm ...

随机推荐

  1. Linux下升级安装Python-3.6.9版本

    1.操作系统信息  (1)cat /etc/redhat-releas (2)Red Hat Enterprise Linux Server release 6.0 (Santiago) 2.安装开发 ...

  2. Java:字符编码

    常用的字符编码 UFT-8 ISO-8859-1 GBK/GBK2312

  3. 两个jquery编写插件实例

    (1) 封装基于jq弹窗插件   相信码友们对于$.fn.extexd();$.extend()以及$.fn.custom和$.custom都有一定的了解:我阐述一下我自己对于$.fn.custom和 ...

  4. SSH自动登录脚本

    原创转载请注明出处:https://www.cnblogs.com/agilestyle/p/11926792.html vi app-stg.sh #!/usr/bin/expect -f #aut ...

  5. 【GDOI2014模拟】网格

    题目 某城市的街道呈网格状,左下角坐标为A(0, 0),右上角坐标为B(n, m),其中n >= m.现在从A(0, 0)点出发,只能沿着街道向正右方或者正上方行走,且不能经过图示中直线左上方的 ...

  6. es6的扩展运算符

    扩展运算符用三个点号表示,功能是把数组或类数组对象展开成一系列用逗号隔开的值,扩展运算符有几点作用: 一,展开数组 //展开数组 let a = [1,2,3,4,5], b = [...a,6,7] ...

  7. javascript之alter的坑

    1.注意在使用alert返回两数之和时,会出现0.1+0.2并不等于0.3的bug 解决方法: var f=0.1; var g=0.2; alert((parseFloat(f)*100+parse ...

  8. (24)Python实现递归生成或者删除一个文件目录及文件

    def removeDir(dirPath): ''' Created by Wu Yongcong 2017-8-18 :param dirPath: :return: ''' if not os. ...

  9. Web开发者易犯的五大严重错误

    无论你是编程高手,还是技术爱好者,在进行Web开发过程中,总避免不了犯各种各样的错误. 犯了错误,可以改正.但如果犯了某些错误,则会带来重大损失.遗憾.令人惊讶的是,这些错误往往是最普通,最容易避免. ...

  10. Retrotranslator使用简介(JDK1.5->1.4)

      Retrotranslator是一个可以把JDK1.5(6)下编译的类(或包)转译成JDK1.4下可以识别的类(包)的工具. 为现在还用JDK1.4呢?我想无非是现在的大部分Java Web应用是 ...