现在讲一下今天要完成和前面android中字母导航和PinnedHeaderListView(listview头部固定)功能差不多,今天实现的功能是上篇的另一种实现方式,有兴趣的可以看一下

千言万语抵不过一张效果图:

上面右侧字母效果图有没有眼前一亮,如果没接触过这方面的童鞋,都不知道从哪里入手,下面一起来看一下,上面右侧字母的效果图是怎样实现的

讲一下滑动字母里面相关效果

1、有明显字体大小变化

2、字母有明显渐变效果

3、字母在指定的区域内左右滑动,字母有明显伸缩变化

相关技术

主要使用贝塞尔曲线二阶函数,运行效果图如下:

二阶贝塞尔曲线公式:

解释一下公式里面变量相关含义

B(t)表示t时间时点的坐标值(比如x值 或 y值)

P0为起点

P1为控制点

P2为终点

结合贝塞尔曲线二阶函数和下面那张图一起来看代码,这个就不难理解了

上图描述是右侧字母效果图运动的整个轨迹,里面都标的比较清楚了,如果上图看不清楚,可以下载下来再看,这个就比较清楚了。结合工程中LetterIndexer这个类理解起来比较容易了,下面是LetterIndexer类代码

public class LetterIndexer extends View {public interface OnTouchLetterChangedListener {void onTouchLetterChanged(String s, int index);void onTouchActionUp(String s);}private Context mContext;// 向右偏移多少画字符, default 30private float mWidthOffset = 30.0f;// 最小字体大小private int mMinFontSize = 24;// 最大字体大小private int mMaxFontSize = 48;// 提示字体大小private int mTipFontSize = 52;// 提示字符的额外偏移private float mAdditionalTipOffset = 20.0f;// 贝塞尔曲线控制的高度private float mMaxBezierHeight = 150.0f;// 贝塞尔曲线单侧宽度private float mMaxBezierWidth = 240.0f;// 贝塞尔曲线单侧模拟线量private int mMaxBezierLines = 32;// 列表字符颜色private int mFontColor = 0xffffffff;  //白色// 提示字符颜色
//  int  mTipFontColor = 0xff3399ff;int mTipFontColor = 0xffd33e48; //金private OnTouchLetterChangedListener mListener;private String[] constChar = {"#", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"};private int mConLength = 0;private int mChooseIndex = -1;private Paint mPaint = new Paint();private PointF mTouch = new PointF();private PointF[] mBezier1;private PointF[] mBezier2;private float mLastOffset[]; // 记录每一个字母的x方向偏移量, 数字<=0private Scroller mScroller;//正在动画private boolean mAnimating = false;//动画的偏移量private float mAnimationOffset;//动画隐藏private boolean mHideAnimation = false;//手指是否抬起private boolean isUp = false;private int mAlpha = 255;/*** 控制距离顶部的距离、底部距离*/private int paddingTop = 0;private int paddingBottom = 0;Handler mHideWaitingHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {if (msg.what == 1) {
//              mScroller.startScroll(0, 0, 255, 0, 1000);mHideAnimation = true;mAnimating = false;                                                        //动画mAnimating=false onDraw触发LetterIndexer.this.invalidate();return;}super.handleMessage(msg);}};public LetterIndexer(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);initData(context, attrs);}public LetterIndexer(Context context, AttributeSet attrs) {super(context, attrs);initData(context, attrs);}public LetterIndexer(Context context) {super(context);initData(null, null);}private void initData(Context context, AttributeSet attrs) {if (context != null && attrs != null) {TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LetterIndexer, 0, 0);mWidthOffset = a.getDimension(R.styleable.LetterIndexer_widthOffset, mWidthOffset);mMinFontSize = a.getInteger(R.styleable.LetterIndexer_minFontSize, mMinFontSize);mMaxFontSize = a.getInteger(R.styleable.LetterIndexer_maxFontSize, mMaxFontSize);mTipFontSize = a.getInteger(R.styleable.LetterIndexer_tipFontSize, mTipFontSize);mMaxBezierHeight = a.getDimension(R.styleable.LetterIndexer_maxBezierHeight, mMaxBezierHeight);mMaxBezierWidth = a.getDimension(R.styleable.LetterIndexer_maxBezierWidth, mMaxBezierWidth);mMaxBezierLines = a.getInteger(R.styleable.LetterIndexer_maxBezierLines, mMaxBezierLines);mAdditionalTipOffset = a.getDimension(R.styleable.LetterIndexer_additionalTipOffset, mAdditionalTipOffset);mFontColor = a.getColor(R.styleable.LetterIndexer_fontColor, mFontColor);mTipFontColor = a.getColor(R.styleable.LetterIndexer_tipFontColor, mTipFontColor);a.recycle();}this.mContext = context;mScroller = new Scroller(getContext());mTouch.x = 0;mTouch.y = -10 * mMaxBezierWidth;mBezier1 = new PointF[mMaxBezierLines];mBezier2 = new PointF[mMaxBezierLines];commonData(0, 0);}/*** 需 注意的是,传值单位是sp* @param top   距离顶部的距离* @param bottom 距离底部的距离*/private void commonData(int top, int bottom) {paddingTop = DisplayUtils.convertDIP2PX(mContext,top);paddingBottom = DisplayUtils.convertDIP2PX(mContext,bottom);mConLength = constChar.length;mLastOffset = new float[mConLength];calculateBezierPoints();}public void setConstChar(String[] constChar,int top, int bottom) {this.constChar = constChar;commonData(top,bottom);}@Overrideprotected void onDraw(Canvas canvas) {// 控件宽高int height = getHeight() - paddingTop - paddingBottom;int width = getWidth();// 单个字母高度float singleHeight = height / (float) constChar.length;int workHeight = paddingTop;if (mAlpha == 0)return;//恢复画笔的默认设置。mPaint.reset();/*** 遍历所以字母内容*/for (int i = 0; i < mConLength; i++) {mPaint.setColor(mFontColor);mPaint.setAntiAlias(true);float xPos = width - mWidthOffset;           // 字母在 x 轴的位置      基本保持不变float yPos = workHeight + singleHeight / 2;  //字母在 y 轴的位置     该值一直在变化// 根据当前字母y的位置计算得到字体大小int fontSize = adjustFontSize(i, yPos);mPaint.setTextSize(fontSize);mAlpha = 255 - fontSize*4;mPaint.setAlpha(mAlpha);if (i == mChooseIndex){mPaint.setColor(Color.parseColor("#F50527"));}// 添加一个字母的高度workHeight += singleHeight;// 绘制字母drawTextInCenter(canvas, constChar[i], xPos + ajustXPosAnimation(i, yPos), yPos);// 如果手指抬起if (isUp) {mListener.onTouchActionUp(constChar[mChooseIndex]);isUp = false;}mPaint.reset();}}/*** @param canvas  画板* @param string  被绘制的字母* @param xCenter 字母的中心x方向位置* @param yCenter 字母的中心y方向位置*/private void drawTextInCenter(Canvas canvas, String string, float xCenter, float yCenter) {FontMetrics fm = mPaint.getFontMetrics();float fontHeight = mPaint.getFontSpacing();float drawY = yCenter + fontHeight / 2 - fm.descent;if (drawY < -fm.ascent - fm.descent)drawY = -fm.ascent - fm.descent;if (drawY > getHeight())drawY = getHeight();mPaint.setTextAlign(Align.CENTER);canvas.drawText(string, xCenter, drawY, mPaint);}private int adjustFontSize(int i, float yPos) {// 根据水平方向偏移量计算出一个放大的字号float adjustX = Math.abs(ajustXPosAnimation(i, yPos));int adjustSize = (int) ((mMaxFontSize - mMinFontSize) * adjustX / mMaxBezierHeight) + mMinFontSize;return adjustSize;}/*** x 方向的向左偏移量** @param i    当前字母的索引* @param yPos y方向的初始位置  会变化* @return*/private float ajustXPosAnimation(int i, float yPos) {float offset;if (this.mAnimating || this.mHideAnimation) {// 正在动画中或在做隐藏动画offset = mLastOffset[i];if (offset != 0.0f) {offset += this.mAnimationOffset;if (offset > 0)offset = 0;}} else {// 根据当前字母y方向位置, 计算水平方向偏移量offset = adjustXPos(yPos);// 当前触摸的x方向位置float xPos = mTouch.x;float width = getWidth() - mWidthOffset;width = width - 60;// 字母绘制时向左偏移量 进行修正, offset需要是<=0的值if (offset != 0.0f && xPos > width){offset += (xPos - width);}if (offset > 0){offset = 0;}mLastOffset[i] = offset;}return offset;}private float adjustXPos(float yPos) {float dis = yPos - mTouch.y; // 字母y方向位置和触摸时y值坐标的差值, 距离越小, 得到的水平方向偏差越大if (dis > -mMaxBezierWidth && dis < mMaxBezierWidth) {// 在2个贝赛尔曲线宽度范围以内 (一个贝赛尔曲线宽度是指一个山峰的一边)// 第一段 曲线if (dis > mMaxBezierWidth / 4) {for (int i = mMaxBezierLines - 1; i > 0; i--) {// 从下到上, 逐个计算if (dis == -mBezier1[i].y) // 落在点上return mBezier1[i].x;// 如果距离dis落在两个贝塞尔曲线模拟点之间, 通过三角函数计算得到当前dis对应的x方向偏移量if (dis > -mBezier1[i].y && dis < -mBezier1[i - 1].y) {return (dis + mBezier1[i].y) * (mBezier1[i - 1].x - mBezier1[i].x) / (-mBezier1[i - 1].y + mBezier1[i].y) + mBezier1[i].x;}}return mBezier1[0].x;}// 第三段 曲线, 和第一段曲线对称if (dis < -mMaxBezierWidth / 4) {for (int i = 0; i < mMaxBezierLines - 1; i++) {// 从上到下if (dis == mBezier1[i].y) // 落在点上return mBezier1[i].x;// 如果距离dis落在两个贝塞尔曲线模拟点之间, 通过三角函数计算得到当前dis对应的x方向偏移量if (dis > mBezier1[i].y && dis < mBezier1[i + 1].y) {return (dis - mBezier1[i].y) * (mBezier1[i + 1].x - mBezier1[i].x) / (mBezier1[i + 1].y - mBezier1[i].y) + mBezier1[i].x;}}return mBezier1[mMaxBezierLines - 1].x;}// 第二段 峰顶曲线for (int i = 0; i < mMaxBezierLines - 1; i++) {if (dis == mBezier2[i].y)return mBezier2[i].x;// 如果距离dis落在两个贝塞尔曲线模拟点之间, 通过三角函数计算得到当前dis对应的x方向偏移量if (dis > mBezier2[i].y && dis < mBezier2[i + 1].y) {return (dis - mBezier2[i].y) * (mBezier2[i + 1].x - mBezier2[i].x) / (mBezier2[i + 1].y - mBezier2[i].y) + mBezier2[i].x;}}return mBezier2[mMaxBezierLines - 1].x;}return 0.0f;}@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {final int action = event.getAction();final float y = event.getY();final int oldmChooseIndex = mChooseIndex;final OnTouchLetterChangedListener listener = mListener;/*** 计算除去paddingTop后,用户点击不同位置对应的字母索引*/final int c = (int) ((y-paddingTop) / (getHeight()-paddingTop-paddingBottom) * constChar.length);switch (action) {case MotionEvent.ACTION_DOWN:if (this.getWidth() > mWidthOffset) {if (event.getX() < this.getWidth() - mWidthOffset)return false;}if (y < paddingTop || c<0 || y > getHeight()-paddingBottom){return false;}mHideWaitingHandler.removeMessages(1);mScroller.abortAnimation();mAnimating = false;mHideAnimation = false;mAlpha = 255;mTouch.x = event.getX();mTouch.y = event.getY();if (oldmChooseIndex != c && listener != null) {if (c > 0 && c < constChar.length) {listener.onTouchLetterChanged(constChar[c],c);mChooseIndex = c;}}invalidate();break;case MotionEvent.ACTION_MOVE:mTouch.x = event.getX();mTouch.y = event.getY();invalidate();if (oldmChooseIndex != c && listener != null) {if (c >= 0 && c < constChar.length) {listener.onTouchLetterChanged(constChar[c],c);mChooseIndex = c;}}break;case MotionEvent.ACTION_UP:mTouch.x = event.getX();mTouch.y = event.getY();isUp = true;mScroller.startScroll(0, 0, (int) mMaxBezierHeight, 0, 2000);mAnimating = true;postInvalidate();break;}return true;}@Overridepublic void computeScroll() {super.computeScroll();if (mScroller.computeScrollOffset()) {if (mAnimating) {float x = mScroller.getCurrX();mAnimationOffset = x;} else if (mHideAnimation) {mAlpha = 255 - (int) mScroller.getCurrX();}invalidate();} else if (mScroller.isFinished()) {if (mAnimating) {mHideWaitingHandler.sendEmptyMessage(1);} else if (mHideAnimation) {mHideAnimation = false;this.mChooseIndex = -1;mTouch.x = -10000;mTouch.y = -10000;}}}public void setOnTouchLetterChangedListener(OnTouchLetterChangedListener listener) {this.mListener = listener;}/*** 计算出所有贝塞尔曲线上的点* 个数为 mMaxBezierLines * 2 = 64*/private void calculateBezierPoints() {PointF mStart = new PointF();   // 开始点PointF mEnd = new PointF();        // 结束点PointF mControl = new PointF(); // 控制点// 计算第一段红色部分 贝赛尔曲线的点// 开始点mStart.x = 0.0f;mStart.y = -mMaxBezierWidth;// 控制点mControl.x = 0.0f;mControl.y = -mMaxBezierWidth / 2;// 结束点mEnd.x = -mMaxBezierHeight / 2;mEnd.y = -mMaxBezierWidth / 4;mBezier1[0] = new PointF();mBezier1[mMaxBezierLines - 1] = new PointF();mBezier1[0].set(mStart);mBezier1[mMaxBezierLines - 1].set(mEnd);for (int i = 1; i < mMaxBezierLines - 1; i++) {mBezier1[i] = new PointF();mBezier1[i].x = calculateBezier(mStart.x, mEnd.x, mControl.x, i / (float) mMaxBezierLines);mBezier1[i].y = calculateBezier(mStart.y, mEnd.y, mControl.y, i / (float) mMaxBezierLines);}// 计算第二段蓝色部分 贝赛尔曲线的点mStart.y = -mMaxBezierWidth / 4;mStart.x = -mMaxBezierHeight / 2;mControl.y = 0.0f;mControl.x = -mMaxBezierHeight;mEnd.y = mMaxBezierWidth / 4;mEnd.x = -mMaxBezierHeight / 2;mBezier2[0] = new PointF();mBezier2[mMaxBezierLines - 1] = new PointF();mBezier2[0].set(mStart);mBezier2[mMaxBezierLines - 1].set(mEnd);for (int i = 1; i < mMaxBezierLines - 1; i++) {mBezier2[i] = new PointF();mBezier2[i].x = calculateBezier(mStart.x, mEnd.x, mControl.x, i / (float) mMaxBezierLines);mBezier2[i].y = calculateBezier(mStart.y, mEnd.y, mControl.y, i / (float) mMaxBezierLines);}}/*** 贝塞尔曲线核心算法** @param start* @param end* @param control* @param val* @return 公式及动图, 维基百科: https://en.wikipedia.org/wiki/B%C3%A9zier_curve* 中文可参考此网站: http://blog.csdn.net/likendsl/article/details/7852658*/private float calculateBezier(float start, float end, float control, float val) {float t = val;float s = 1 - t;float ret = start * s * s + 2 * control * s * t + end * t * t;return ret;}
}

上面代码是实现字母右侧滑动效果的核心类,里面都有注释了,记得这个类要结合上图来看,会发现哦原来是这样子,很溜吧。所以学好数学是很必要的

attrs.xml文件内容

<?xml version="1.0" encoding="utf-8"?>
<resources><declare-styleable name="LetterIndexer"><attr name="widthOffset" format="dimension" /><attr name="minFontSize" format="integer" /><attr name="maxFontSize" format="integer" /><attr name="tipFontSize" format="integer" /><attr name="maxBezierHeight" format="dimension" /><attr name="maxBezierWidth" format="dimension" /><attr name="maxBezierLines" format="integer" /><attr name="additionalTipOffset" format="dimension" /><attr name="fontColor" format="color" /><attr name="tipFontColor" format="color" /></declare-styleable></resources>

主MainActivity

public class MainActivity extends Activity {private PinnedHeaderListView pinnedHeaderListView;private ArrayList<Person> persons;                                //英雄好汉列表数据集 (除过分组标签,列表所有数据,无序)private LinkedHashMap<String, List<Person>> personMpas;           //英雄好汉列表 分组标签对应的数据集合(有序)private PinnedHeaderListViewAdapter<Person> adapter;              //英雄好汉列表适配器private LetterIndexer letterIndexer;private TextView tv_index_center;private Handler mHandler = new Handler();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();lister();//初始化数据initData();}private void initView() {pinnedHeaderListView = (PinnedHeaderListView)findViewById(R.id.pinnedheader_listview);pinnedHeaderListView.setPinnedHeaderView(this.getLayoutInflater().inflate(R.layout.pinnedheaderlistview_header_layout, pinnedHeaderListView, false));letterIndexer = (LetterIndexer) findViewById(R.id.letter_index);tv_index_center = (TextView) findViewById(R.id.tv_index_center);}private void lister() {letterIndexer.setOnTouchLetterChangedListener(new LetterIndexer.OnTouchLetterChangedListener() {@Overridepublic void onTouchLetterChanged(String letter,int index) {// 从集合中查找第一个拼音首字母为letter的索引, 进行跳转for (int i = 0; i < persons.size(); i++) {Person person = persons.get(i);String s = person.getLetter();if(TextUtils.equals(s, letter)){// 匹配成功, 中断循环, 将列表移动到指定的位置pinnedHeaderListView.setSelection(i);break;}}}@Overridepublic void onTouchActionUp(String letter) {showLetter(letter);}});}/*** 显示字母提示** @param letter*/protected void showLetter(String letter) {tv_index_center.setVisibility(View.VISIBLE);tv_index_center.setText(letter);mHandler.removeCallbacksAndMessages(null);mHandler.postDelayed(new Runnable() {@Overridepublic void run() {// 隐藏tv_index_center.setVisibility(View.GONE);}}, 2000);}protected void initData() {InternalStorageUtils.asynReadInternalFile(this, "names.config", new AsyncResonseHandler() {@Overrideprotected void onSuccess(String content) {super.onSuccess(content);try {Gson gson = new Gson();HeroPerson hero = gson.fromJson(content,new TypeToken<HeroPerson>() {}.getType());List<PersonList> personList = hero.getSections();persons = new ArrayList<Person>();personMpas = new LinkedHashMap<String, List<Person>>();//特殊字符List<Person> specialChar = new ArrayList<Person>();Person charPerson = null;for (int i=0; i<5; i++){charPerson = new Person();charPerson.setName("#特殊字符"+i);charPerson.setLetter("#");specialChar.add(charPerson);}personMpas.put("特殊字符", specialChar);persons.addAll(specialChar);//得到右侧字母索引的内容int letterLength = personList.size()+ personMpas.size();String[] constChar = new String[letterLength];constChar[0] = "#";List<Person> personItems;for (int i = 0; i < personList.size(); i++) {personItems = personList.get(i).getPersons();persons.addAll(personItems);personMpas.put(personList.get(i).getIndex(), personItems);constChar[i+1] = personList.get(i).getIndex();}adapter = new PinnedHeaderListViewAdapter<Person>(MainActivity.this, personMpas, pinnedHeaderListView,letterIndexer,constChar, 20, 20);pinnedHeaderListView.setOnScrollListener(adapter);pinnedHeaderListView.setAdapter(adapter);} catch (Exception e) {e.printStackTrace();}}});}}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:huahua="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:background="@color/bg_gray_main" ><TextViewandroid:id="@+id/tv_title"android:layout_width="match_parent"android:layout_height="50dp"android:gravity="center"android:background="#96B1B4"android:text="标题内容"/><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"><include layout="@layout/person_pinned_header_listview"android:id="@+id/pinnedheader_listview"/><cn.com.huahua.pinnedheaderlistview.ui.LetterIndexerandroid:id="@+id/letter_index"android:layout_width="match_parent"android:layout_height="match_parent"huahua:widthOffset="15dip"huahua:minFontSize="32"huahua:maxFontSize="77"huahua:tipFontSize="72"huahua:maxBezierHeight="150dip"huahua:maxBezierWidth="180dip"huahua:additionalTipOffset="40dip"huahua:fontColor="#2278BF" /><TextViewandroid:id="@+id/tv_index_center"android:visibility="gone"android:layout_width="100dp"android:layout_height="100dp"android:layout_centerInParent="true"android:textColor="#FFFFFF"android:gravity="center"android:textSize="36sp"android:background="@drawable/alpha_center_corner"android:text="A" /></RelativeLayout>
</LinearLayout>

下面提供一下源码

源码下载

参考资料

http://blog.csdn.net/likendsl/article/details/7852658

http://www.jcodecraeer.com/a/opensource/2015/1104/3656.html

android中实现更溜的字母导航索引相关推荐

  1. android wifi 问题是什么意思,如何解决WiFi连接问题故障在Android中为更好的互联网...

    您的工作时间后到家,打开无线网络连接你的手机,等待 - 但是什么也没有发生! 你试图将其关闭并重新打开它,而且也没有改变. 这听起来很可悲,不是吗? 好了,不用担心. 你是不是谁在Android的这些 ...

  2. Android获取通讯录联系人,右侧字母滑动索引

    一.跳转系统通讯录 普通的联系人列表,无法选择联系人或回调,只能查看详情 Intent intent = new Intent();intent.setClassName("com.andr ...

  3. Android中如何优雅的定义常量

    本篇博客是笔者的第一篇博客,其实很早之前就有了写博客的想法.但是奈何万事开头难,一直没有下定决心.随着自己积累的知识不断增多,如果不进行总结,不进行思考归纳,有些知识会经常遗忘.对于某些难题,可能当时 ...

  4. Android自定义View——实现字母导航栏

    思路分析 1.自定义View实现字母导航栏 2.ListView实现联系人列表 3.字母导航栏滑动事件处理 4.字母导航栏与中间字母的联动 5.字母导航栏与ListView的联动 效果展示 实现步骤 ...

  5. Android字母导航示例

    文章转自:http://blog.csdn.net/qq_26787115/article/details/52502787 论字母导航的重要性,我们来实现一个联系人字母导航列表吧! 说起这个字母导航 ...

  6. Android实现首字母导航条(仿微信)

    本博客介绍Android实现首字母导航条,先看张效果图,具体怎么实现看代码吧 具体的步骤 1.整体布局的显示 2. 实现A-Z的分组 3. 自定义A-Z的导航条 4. 中间显示/隐藏触摸到导航条具体的 ...

  7. Android仿微信自定义字母导航栏

    自定义侧边字母导航栏,根据实际字母高度进行显示 先上效果图 导航栏                                                                    ...

  8. Android中导航栏之溢出菜单OverflowMenu

    Toolbar系列文章导航 Android中导航栏之Toolbar的使用 Android中导航栏之溢出菜单OverflowMenu Android中导航栏之搜索框SearchView Android中 ...

  9. Android Studio TV开发教程(十五) Android N及更早版本中的建议

    Android Studio TV开发教程 (转自Android官网https://developer.android.com/training/tv/start) 文章源自:光谷佳武 https:/ ...

最新文章

  1. 北航机器人研究所 裴旭_三年亏损近29亿!北航硕士痴迷平衡车,让“中国智造”走向世界...
  2. 中国首个火星探测器天问一号发射成功!
  3. C++ Primer 5th笔记(chap 17 标准库特殊设施)bitset操作
  4. java项目大小_Java项目仅在调整窗口大小之后出现
  5. svm最大间隔函数及目标函数推导
  6. Python实训day02am【列表、字符串、字符集】
  7. 暑期训练日志----2018.8.25
  8. 索引sql server_SQL Server索引设计基础和准则
  9. 减少HTTP请求之将图片转成二进制并生成Base64编码,可以在网页中通过url查看图片(大型网站优化技术)...
  10. mysql数据库外键的作用
  11. 在Cfree5里使用多行快捷注释的方法
  12. 开发中,GA、Beta、GA、Trial到底是什么含义
  13. 合伙企业对外债务如何承担
  14. ChatGPT学习-如何向ChatGPT提问
  15. 人手一份!八大危险作业操作规程、作业票证(模板)、安全培训齐了~
  16. 大数据在车联网行业的实践与应用
  17. BIO、NIO、AIO 有什么区别?
  18. 1年工作经验跳槽项目总监,看到他的数据可视化报表,我沉默了
  19. 复工之后:员工如何改善网络安全?
  20. win10 虚拟环境

热门文章

  1. 快手极速版自动翻视频
  2. 小程序反编译-获取源码
  3. 微信小程序实现一键返回顶部
  4. Docker构建基于阿里云epel源的CentOS7 epel源镜像
  5. ts报错:Object is possibly ‘undefined‘.Vetur(2532)
  6. DeFi热潮下的安全隐患:流动性危机恐将造成连锁反应 | 非正式会谈
  7. [vim与gvim技巧]vimgvim技巧大全(1)
  8. 从浅到深,数据分析人的学习书籍!
  9. 从零入手体验指纹膜代打卡全过程
  10. springboot源码编译