- 浏览: 2180357 次
- 性别:
- 来自: 北京
文章分类
- 全部博客 (1240)
- mac/IOS (287)
- flutter (1)
- J2EE (115)
- android基础知识 (582)
- android中级知识 (55)
- android组件(Widget)开发 (18)
- android 错误 (21)
- javascript (18)
- linux (70)
- 树莓派 (18)
- gwt/gxt (1)
- 工具(IDE)/包(jar) (18)
- web前端 (17)
- java 算法 (8)
- 其它 (5)
- chrome (7)
- 数据库 (8)
- 经济/金融 (0)
- english (2)
- HTML5 (7)
- 网络安全 (14)
- 设计欣赏/设计窗 (8)
- 汇编/C (8)
- 工具类 (4)
- 游戏 (5)
- 开发频道 (5)
- Android OpenGL (1)
- 科学 (4)
- 运维 (0)
- 好东西 (6)
- 美食 (1)
最新评论
-
liangzai_cool:
请教一下,文中,shell、C、Python三种方式控制led ...
树莓派 - MAX7219 -
jiazimo:
...
Kafka源码分析-序列5 -Producer -RecordAccumulator队列分析 -
hp321:
Windows该命令是不是需要安装什么软件才可以?我试过不行( ...
ImageIO读jpg的时候出现javax.imageio.IIOException: Unsupported Image Type -
hp321:
Chenzh_758 写道其实直接用一下代码就可以解决了:JP ...
ImageIO读jpg的时候出现javax.imageio.IIOException: Unsupported Image Type -
huanghonhpeng:
大哥你真强什么都会,研究研究。。。。小弟在这里学到了很多知识。 ...
android 浏览器
很早之前看过有人求助以下这个效果是如何实现的,
也就是侧滑菜单的一个折叠效果,其实关于这个效果的实现,谷歌的一名工程师已经完成,并开放源码到devbytes上面了。如下面所示:
地址是: https://android.googlesource.com/platform/development/+/master/samples/devbytes/graphics/,还有相应的视频解说:DevBytes: Folding Layout - YouTube,这个想看的话需要fq。运行效果如下:
那后来就有人在此基础之上添加了侧滑菜单效果,包括DrawerLayout和PaneLayout两种,此项目的github地址是Folding-Android,运行效果如下:
下面主要围绕实现原理来展开,以谷歌的devbytes为例,看一下它的自定义ViewGroup:FoldingLayout。代码如下,
在变量中,看到使用了Rect、Matrix、LinearGradient、Bitmap等graphics类,主要是负责绘图,当然还有最基础的canvas和paint。在FoldingLayout中,大体可以分为三个部分,分别以三个方法为代表:1、prepareFold(Orientation orientation, float anchorFactor, int numberOfFolds) 初始化准备,为变量赋值2、calculateMatrices() 计算转换矩阵,用于绘制每个单独的折叠部分3、dispatchDraw(Canvas canvas) 重载ViewGroup的方法,绘制图形
然后进入各个方法,在calculateMatrices()方法的最后,有一个下面的方法: /* Sets the shadow and bitmap transformation matrices.*/ mMatrix[x].setPolyToPoly(mSrc, 0, mDst, 0, NUM_OF_POLY_POINTS / 2); 这个方法是Matrix类里面的,那它是做什么用的呢?首先肯定是对矩阵进行变换,那会是什么效果呢?结合折叠的效果来说一下。在api中,这个类是这样描述的,
这个方法就是设置指定的矩阵的src点映射到指定的dst点。这些点代表一个float数组,每一点由两个float值组成。比如一个矩形,用数组可以这样表示:
[x0,y0,x1,y1,x2,y2,x3,y3],图示:
再来回顾一下折叠效果是如何实现的,可以把它拆分成如下图所示:
对于折叠一次的图形,可以把其分成两个部分,也就是先对图形进行切割,之后再对每个图形做拉伸变换,所以很容易想到使用Matrix类实现,用的正是setPolyToPoly()方法。下面以一个实例说一下setPolyToPoly()方法的使用,代码如下:
在数组中,设置的矩形四个顶点的坐标,在目的矩形的坐标中,改变了右上以及右下的坐标,使其分别向下和向上移动50像素。所以效果如下:
可以看到上图正是一个折叠效果(看图别晕,大自然是不是真的很神奇)。FoldingLayout就是在这个基础之上延伸而来的,接下来就看看它是如何具体实现的。1、首先看一下 方法,
这个方法从名字就可以看出来是用于数据初始化,开始时初始化setPolyToPoly方法参数数组,然后设置了画笔、折叠方向、折叠线附近的折叠效果灯。最重要的就是初始化这两个参数: private Rect[] mFoldRectArray:
private Matrix [] mMatrix: 他们分别根据折叠的数目来赋值。
2、calculateMatrices() 这个方法是计算的核心部分,
首先根据mFoldFactor得到cTranslationFactor,mFoldFactor是根据手指滑动的距离得到的,是一个很小的数值,如果滑动距离小的话每次只有0.0几的变化。所以得到的cTranslationFactor就像是一个百分比一样,来计算相对的数值。然后根据cTranslationFactor计算出translatedDistance、mFoldDrawWidth、depth等。详细说明已经在代码中注释了,在此就不细说了,如图:
3、dispatchDraw(Canvas canvas) 最后就是把图形绘制出来,这个方法在之前的自定义ViewGroup博客中已经提到过,如果不重写这个方法,那上面的工作室无法显示出来的。看代码:
其实这个方法最主要的两个地方就是,canvas.concat( mMatrix[x])和canvas.clipRect(0, 0, src. right - src.left, src. bottom- src. top)。首先在最外层是一个for循环,这个循环根据折叠的数目迭代,本例中这个数值为2,所以将迭代两次。接着从mFoldRectArray数组中取出一个Rect赋值给src,然后就调用canvas.concat( mMatrix[x])方法了。我们知道,这个Matrix刚才已经使用setPolyToPoly方法进行变换了,那如何将变换后的矩阵的效果应用到视图上呢,就是使用canvas.concat( mMatrix[x])方法,如果没有这个方法,那就不会看到折叠的效果。然后就是canvas.clipRect(0, 0, src.right - src.left, src.bottom - src.top)方法了,这个方法用于对图形进行裁剪,显示出每一个折叠的视图。需要注意的地方是这个方法要在后续画画操作之前进行剪切才能生效,这也就是为什么这个方法在super.dispatchDraw(canvas)之前调用。接着还需要对视图进行平移操作,canvas.translate(-src.left, 0),不然的话会显示两个相同的图片。
也就是侧滑菜单的一个折叠效果,其实关于这个效果的实现,谷歌的一名工程师已经完成,并开放源码到devbytes上面了。如下面所示:
地址是: https://android.googlesource.com/platform/development/+/master/samples/devbytes/graphics/,还有相应的视频解说:DevBytes: Folding Layout - YouTube,这个想看的话需要fq。运行效果如下:
那后来就有人在此基础之上添加了侧滑菜单效果,包括DrawerLayout和PaneLayout两种,此项目的github地址是Folding-Android,运行效果如下:
下面主要围绕实现原理来展开,以谷歌的devbytes为例,看一下它的自定义ViewGroup:FoldingLayout。代码如下,
/* * Copyright (C) 2013 The <a href="http://www.it165.net/pro/ydad/" target="_blank" class="keylink">Android</a> Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.foldinglayout; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Rect; import android.graphics.Shader.TileMode; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; /** * The folding layout where the number of folds, the anchor point and the * orientation of the fold can be specified. Each of these parameters can * be modified individually and updates and resets the fold to a default * (unfolded) state. The fold factor varies between 0 (completely unfolded * flat image) to 1.0 (completely folded, non-visible image). * * This layout throws an exception if there is more than one child added to the view. * For more complicated view hierarchy's inside the folding layout, the views should all * be nested inside 1 parent layout. * * This layout folds the contents of its child in real time. By applying matrix * transformations when drawing to canvas, the contents of the child may change as * the fold takes place. It is important to note that there are jagged edges about * the perimeter of the layout as a result of applying transformations to a rectangle. * This can be avoided by having the child of this layout wrap its content inside a * 1 pixel transparent border. This will cause an anti-aliasing like effect and smoothen * out the edges. * */ public class FoldingLayout extends ViewGroup { public static enum Orientation { VERTICAL, HORIZONTAL } private final String FOLDING_VIEW_EXCEPTION_MESSAGE = "Folding Layout can only 1 child at " + "most"; private final float SHADING_ALPHA = 0.8f; private final float SHADING_FACTOR = 0.5f; private final int DEPTH_CONSTANT = 1500; private final int NUM_OF_POLY_POINTS = 8; private Rect[] mFoldRectArray; private Matrix [] mMatrix; private Orientation mOrientation = Orientation.HORIZONTAL; private float mAnchorFactor = 0; private float mFoldFactor = 0; private int mNumberOfFolds = 2; private boolean mIsHorizontal = true; private int mOriginalWidth = 0; private int mOriginalHeight = 0; private float mFoldMaxWidth = 0; private float mFoldMaxHeight = 0; private float mFoldDrawWidth = 0; private float mFoldDrawHeight = 0; private boolean mIsFoldPrepared = false; private boolean mShouldDraw = true; private Paint mSolidShadow; private Paint mGradientShadow; private LinearGradient mShadowLinearGradient; private Matrix mShadowGradientMatrix; private float [] mSrc; private float [] mDst; private OnFoldListener mFoldListener; private float mPreviousFoldFactor = 0; private Bitmap mFullBitmap; private Rect mDstRect; public FoldingLayout(Context context) { super(context); } public FoldingLayout(Context context, AttributeSet attrs) { super(context, attrs); } public FoldingLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected boolean addViewInLayout(View child, int index, LayoutParams params, boolean preventRequestLayout) { throwCustomException(getChildCount()); boolean returnValue = super.addViewInLayout(child, index, params, preventRequestLayout); return returnValue; } @Override public void addView(View child, int index, LayoutParams params) { throwCustomException(getChildCount()); super.addView(child, index, params); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { View child = getChildAt(0); measureChild(child,widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { View child = getChildAt(0); child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight()); updateFold(); } /** * The custom exception to be thrown so as to limit the number of views in this * layout to at most one. */ private class NumberOfFoldingLayoutChildrenException extends RuntimeException { public NumberOfFoldingLayoutChildrenException(String message) { super(message); } } /** Throws an exception if the number of views added to this layout exceeds one.*/ private void throwCustomException (int numOfChildViews) { if (numOfChildViews == 1) { throw new NumberOfFoldingLayoutChildrenException(FOLDING_VIEW_EXCEPTION_MESSAGE); } } public void setFoldListener(OnFoldListener foldListener) { mFoldListener = foldListener; } /** * Sets the fold factor of the folding view and updates all the corresponding * matrices and values to account for the new fold factor. Once that is complete, * it redraws itself with the new fold. */ public void setFoldFactor(float foldFactor) { if (foldFactor != mFoldFactor) { mFoldFactor = foldFactor; calculateMatrices(); invalidate(); } } public void setOrientation(Orientation orientation) { if (orientation != mOrientation) { mOrientation = orientation; updateFold(); } } public void setAnchorFactor(float anchorFactor) { if (anchorFactor != mAnchorFactor) { mAnchorFactor = anchorFactor; updateFold(); } } public void setNumberOfFolds(int numberOfFolds) { if (numberOfFolds != mNumberOfFolds) { mNumberOfFolds = numberOfFolds; updateFold(); } } public float getAnchorFactor() { return mAnchorFactor; } public Orientation getOrientation() { return mOrientation; } public float getFoldFactor() { return mFoldFactor; } public int getNumberOfFolds() { return mNumberOfFolds; } private void updateFold() { prepareFold(mOrientation, mAnchorFactor, mNumberOfFolds); calculateMatrices(); invalidate(); } /** * This method is called in order to update the fold's orientation, anchor * point and number of folds. This creates the necessary setup in order to * prepare the layout for a fold with the specified parameters. Some of the * dimensions required for the folding transformation are also acquired here. * * After this method is called, it will be in a completely unfolded state by default. */ private void prepareFold(Orientation orientation, float anchorFactor, int numberOfFolds) { mSrc = new float[NUM_OF_POLY_POINTS]; mDst = new float[NUM_OF_POLY_POINTS]; mDstRect = new Rect(); mFoldFactor = 0; mPreviousFoldFactor = 0; mIsFoldPrepared = false; mSolidShadow = new Paint(); mGradientShadow = new Paint(); mOrientation = orientation; mIsHorizontal = (orientation == Orientation.HORIZONTAL); if (mIsHorizontal) { mShadowLinearGradient = new LinearGradient(0, 0, SHADING_FACTOR, 0, Color.BLACK, Color.TRANSPARENT, TileMode.CLAMP); } else { mShadowLinearGradient = new LinearGradient(0, 0, 0, SHADING_FACTOR, Color.BLACK, Color.TRANSPARENT, TileMode.CLAMP); } mGradientShadow.setStyle(Style.FILL); mGradientShadow.setShader(mShadowLinearGradient); mShadowGradientMatrix = new Matrix(); mAnchorFactor = anchorFactor; mNumberOfFolds = numberOfFolds; mOriginalWidth = getMeasuredWidth(); mOriginalHeight = getMeasuredHeight(); mFoldRectArray = new Rect[mNumberOfFolds]; mMatrix = new Matrix [mNumberOfFolds]; for (int x = 0; x < mNumberOfFolds; x++) { mMatrix[x] = new Matrix(); } int h = mOriginalHeight; int w = mOriginalWidth; if (FoldingLayoutActivity.IS_JBMR2) { mFullBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(mFullBitmap); getChildAt(0).draw(canvas); } int delta = Math.round(mIsHorizontal ? ((float) w) / ((float) mNumberOfFolds) : ((float) h) /((float) mNumberOfFolds)); /* Loops through the number of folds and segments the full layout into a number * of smaller equal components. If the number of folds is odd, then one of the * components will be smaller than all the rest. Note that deltap below handles * the calculation for an odd number of folds.*/ for (int x = 0; x < mNumberOfFolds; x++) { if (mIsHorizontal) { int deltap = (x + 1) * delta > w ? w - x * delta : delta; mFoldRectArray[x] = new Rect(x * delta, 0, x * delta + deltap, h); } else { int deltap = (x + 1) * delta > h ? h - x * delta : delta; mFoldRectArray[x] = new Rect(0, x * delta, w, x * delta + deltap); } } if (mIsHorizontal) { mFoldMaxHeight = h; mFoldMaxWidth = delta; } else { mFoldMaxHeight = delta; mFoldMaxWidth = w; } mIsFoldPrepared = true; } /* * Calculates the transformation matrices used to draw each of the separate folding * segments from this view. */ private void calculateMatrices() { mShouldDraw = true; if (!mIsFoldPrepared) { return; } /** If the fold factor is 1 than the folding view should not be seen * and the canvas can be left completely empty. */ if (mFoldFactor == 1) { mShouldDraw = false; return; } if (mFoldFactor == 0 && mPreviousFoldFactor > 0) { mFoldListener.onEndFold(); } if (mPreviousFoldFactor == 0 && mFoldFactor > 0) { mFoldListener.onStartFold(); } mPreviousFoldFactor = mFoldFactor; /* Reset all the transformation matrices back to identity before computing * the new transformation */ for (int x = 0; x < mNumberOfFolds; x++) { mMatrix[x].reset(); } float cTranslationFactor = 1 - mFoldFactor; float translatedDistance = mIsHorizontal ? mOriginalWidth * cTranslationFactor : mOriginalHeight * cTranslationFactor; float translatedDistancePerFold = Math.round(translatedDistance / mNumberOfFolds); /* For an odd number of folds, the rounding error may cause the * translatedDistancePerFold to be grater than the max fold width or height. */ mFoldDrawWidth = mFoldMaxWidth < translatedDistancePerFold ? translatedDistancePerFold : mFoldMaxWidth; mFoldDrawHeight = mFoldMaxHeight < translatedDistancePerFold ? translatedDistancePerFold : mFoldMaxHeight; float translatedDistanceFoldSquared = translatedDistancePerFold * translatedDistancePerFold; /* Calculate the depth of the fold into the screen using pythagorean theorem. */ float depth = mIsHorizontal ? (float)Math.sqrt((double)(mFoldDrawWidth * mFoldDrawWidth - translatedDistanceFoldSquared)) : (float)Math.sqrt((double)(mFoldDrawHeight * mFoldDrawHeight - translatedDistanceFoldSquared)); /* The size of some object is always inversely proportional to the distance * it is away from the viewpoint. The constant can be varied to to affect the * amount of perspective. */ float scaleFactor = DEPTH_CONSTANT / (DEPTH_CONSTANT + depth); float scaledWidth, scaledHeight, bottomScaledPoint, topScaledPoint, rightScaledPoint, leftScaledPoint; if (mIsHorizontal) { scaledWidth = mFoldDrawWidth * cTranslationFactor; scaledHeight = mFoldDrawHeight * scaleFactor; } else { scaledWidth = mFoldDrawWidth * scaleFactor; scaledHeight = mFoldDrawHeight * cTranslationFactor; } topScaledPoint = (mFoldDrawHeight - scaledHeight) / 2.0f; bottomScaledPoint = topScaledPoint + scaledHeight; leftScaledPoint = (mFoldDrawWidth - scaledWidth) / 2.0f; rightScaledPoint = leftScaledPoint + scaledWidth; float anchorPoint = mIsHorizontal ? mAnchorFactor * mOriginalWidth : mAnchorFactor * mOriginalHeight; /* The fold along which the anchor point is located. */ float midFold = mIsHorizontal ? (anchorPoint / mFoldDrawWidth) : anchorPoint / mFoldDrawHeight; mSrc[0] = 0; mSrc[1] = 0; mSrc[2] = 0; mSrc[3] = mFoldDrawHeight; mSrc[4] = mFoldDrawWidth; mSrc[5] = 0; mSrc[6] = mFoldDrawWidth; mSrc[7] = mFoldDrawHeight; /* Computes the transformation matrix for each fold using the values calculated above. */ for (int x = 0; x < mNumberOfFolds; x++) { boolean isEven = (x % 2 == 0); if (mIsHorizontal) { mDst[0] = (anchorPoint > x * mFoldDrawWidth) ? anchorPoint + (x - midFold) * scaledWidth : anchorPoint - (midFold - x) * scaledWidth; mDst[1] = isEven ? 0 : topScaledPoint; mDst[2] = mDst[0]; mDst[3] = isEven ? mFoldDrawHeight: bottomScaledPoint; mDst[4] = (anchorPoint > (x + 1) * mFoldDrawWidth) ? anchorPoint + (x + 1 - midFold) * scaledWidth : anchorPoint - (midFold - x - 1) * scaledWidth; mDst[5] = isEven ? topScaledPoint : 0; mDst[6] = mDst[4]; mDst[7] = isEven ? bottomScaledPoint : mFoldDrawHeight; } else { mDst[0] = isEven ? 0 : leftScaledPoint; mDst[1] = (anchorPoint > x * mFoldDrawHeight) ? anchorPoint + (x - midFold) * scaledHeight : anchorPoint - (midFold - x) * scaledHeight; mDst[2] = isEven ? leftScaledPoint: 0; mDst[3] = (anchorPoint > (x + 1) * mFoldDrawHeight) ? anchorPoint + (x + 1 - midFold) * scaledHeight : anchorPoint - (midFold - x - 1) * scaledHeight; mDst[4] = isEven ? mFoldDrawWidth : rightScaledPoint; mDst[5] = mDst[1]; mDst[6] = isEven ? rightScaledPoint : mFoldDrawWidth; mDst[7] = mDst[3]; } /* Pixel fractions are present for odd number of folds which need to be * rounded off here.*/ for (int y = 0; y < 8; y ++) { mDst[y] = Math.round(mDst[y]); } /* If it so happens that any of the folds have reached a point where * the width or height of that fold is 0, then nothing needs to be * drawn onto the canvas because the view is essentially completely * folded.*/ if (mIsHorizontal) { if (mDst[4] <= mDst[0] || mDst[6] <= mDst[2]) { mShouldDraw = false; return; } } else { if (mDst[3] <= mDst[1] || mDst[7] <= mDst[5]) { mShouldDraw = false; return; } } /* Sets the shadow and bitmap transformation matrices.*/ mMatrix[x].setPolyToPoly(mSrc, 0, mDst, 0, NUM_OF_POLY_POINTS / 2); } /* The shadows on the folds are split into two parts: Solid shadows and gradients. * Every other fold has a solid shadow which overlays the whole fold. Similarly, * the folds in between these alternating folds also have an overlaying shadow. * However, it is a gradient that takes up part of the fold as opposed to a solid * shadow overlaying the whole fold.*/ /* Solid shadow paint object. */ int alpha = (int) (mFoldFactor * 255 * SHADING_ALPHA); mSolidShadow.setColor(Color.argb(alpha, 0, 0, 0)); if (mIsHorizontal) { mShadowGradientMatrix.setScale(mFoldDrawWidth, 1); mShadowLinearGradient.setLocalMatrix(mShadowGradientMatrix); } else { mShadowGradientMatrix.setScale(1, mFoldDrawHeight); mShadowLinearGradient.setLocalMatrix(mShadowGradientMatrix); } mGradientShadow.setAlpha(alpha); } @Override protected void dispatchDraw(Canvas canvas) { /** If prepareFold has not been called or if preparation has not completed yet, * then no custom drawing will take place so only need to invoke super's * onDraw and return. */ if (!mIsFoldPrepared || mFoldFactor == 0) { super.dispatchDraw(canvas); return; } if (!mShouldDraw) { return; } Rect src; /* Draws the bitmaps and shadows on the canvas with the appropriate transformations. */ for (int x = 0; x < mNumberOfFolds; x++) { src = mFoldRectArray[x]; /* The canvas is saved and restored for every individual fold*/ canvas.save(); /* Concatenates the canvas with the transformation matrix for the * the segment of the view corresponding to the actual image being * displayed. */ canvas.concat(mMatrix[x]); if (FoldingLayoutActivity.IS_JBMR2) { mDstRect.set(0, 0, src.width(), src.height()); canvas.drawBitmap(mFullBitmap, src, mDstRect, null); } else { /* The same transformation matrix is used for both the shadow and the image * segment. The canvas is clipped to account for the size of each fold and * is translated so they are drawn in the right place. The shadow is then drawn on * top of the different folds using the sametransformation matrix.*/ canvas.clipRect(0, 0, src.right - src.left, src.bottom - src.top); if (mIsHorizontal) { canvas.translate(-src.left, 0); } else { canvas.translate(0, -src.top); } super.dispatchDraw(canvas); if (mIsHorizontal) { canvas.translate(src.left, 0); } else { canvas.translate(0, src.top); } } /* Draws the shadows corresponding to this specific fold. */ if (x % 2 == 0) { canvas.drawRect(0, 0, mFoldDrawWidth, mFoldDrawHeight, mSolidShadow); } else { canvas.drawRect(0, 0, mFoldDrawWidth, mFoldDrawHeight, mGradientShadow); } canvas.restore(); } } }
在变量中,看到使用了Rect、Matrix、LinearGradient、Bitmap等graphics类,主要是负责绘图,当然还有最基础的canvas和paint。在FoldingLayout中,大体可以分为三个部分,分别以三个方法为代表:1、prepareFold(Orientation orientation, float anchorFactor, int numberOfFolds) 初始化准备,为变量赋值2、calculateMatrices() 计算转换矩阵,用于绘制每个单独的折叠部分3、dispatchDraw(Canvas canvas) 重载ViewGroup的方法,绘制图形
然后进入各个方法,在calculateMatrices()方法的最后,有一个下面的方法: /* Sets the shadow and bitmap transformation matrices.*/ mMatrix[x].setPolyToPoly(mSrc, 0, mDst, 0, NUM_OF_POLY_POINTS / 2); 这个方法是Matrix类里面的,那它是做什么用的呢?首先肯定是对矩阵进行变换,那会是什么效果呢?结合折叠的效果来说一下。在api中,这个类是这样描述的,
这个方法就是设置指定的矩阵的src点映射到指定的dst点。这些点代表一个float数组,每一点由两个float值组成。比如一个矩形,用数组可以这样表示:
[x0,y0,x1,y1,x2,y2,x3,y3],图示:
再来回顾一下折叠效果是如何实现的,可以把它拆分成如下图所示:
对于折叠一次的图形,可以把其分成两个部分,也就是先对图形进行切割,之后再对每个图形做拉伸变换,所以很容易想到使用Matrix类实现,用的正是setPolyToPoly()方法。下面以一个实例说一下setPolyToPoly()方法的使用,代码如下:
package com.example.demo; import android.support.v7.app.ActionBarActivity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Matrix; import android.os.Bundle; import android.view.View; public class MainActivity extends ActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new MyView(this)); } class MyView extends View { private Bitmap bitmap; private Matrix mMatrix; public MyView(Context context) { super(context); bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.a); mMatrix = new Matrix(); } @Override protected void onDraw(Canvas canvas) { float[] src = new float[] { 0, 0, // 左上 bitmap.getWidth(), 0,// 右上 bitmap.getWidth(), bitmap.getHeight(),// 右下 0, bitmap.getHeight() };// 左下 float[] dst = new float[] { 0, 0, bitmap.getWidth(), 50, bitmap.getWidth(), bitmap.getHeight() - 50, 0, bitmap.getHeight() }; mMatrix.setPolyToPoly(src, 0, dst, 0, src.length >> 1); canvas.drawBitmap(bitmap, mMatrix, null); canvas.drawBitmap(bitmap, 0, bitmap.getHeight(), null); } } }
在数组中,设置的矩形四个顶点的坐标,在目的矩形的坐标中,改变了右上以及右下的坐标,使其分别向下和向上移动50像素。所以效果如下:
可以看到上图正是一个折叠效果(看图别晕,大自然是不是真的很神奇)。FoldingLayout就是在这个基础之上延伸而来的,接下来就看看它是如何具体实现的。1、首先看一下 方法,
/** * This method is called in order to update the fold's orientation, anchor * point and number of folds. This creates the necessary setup in order to * prepare the layout for a fold with the specified parameters. Some of the * dimensions required for the folding transformation are also acquired here. * * After this method is called, it will be in a completely unfolded state by default. */ private void prepareFold(Orientation orientation, float anchorFactor, int numberOfFolds) { //setPolyToPoly方法参数 mSrc = new float[NUM_OF_POLY_POINTS]; mDst = new float[NUM_OF_POLY_POINTS]; // mDstRect = new Rect(); mFoldFactor = 0; mPreviousFoldFactor = 0; //是否折叠 mIsFoldPrepared = false; //画笔 mSolidShadow = new Paint(); mGradientShadow = new Paint(); //方向 mOrientation = orientation; mIsHorizontal = (orientation == Orientation.HORIZONTAL); //折叠线附近的效果 if (mIsHorizontal) { mShadowLinearGradient = new LinearGradient(0, 0, SHADING_FACTOR, 0, Color.RED, Color.TRANSPARENT, TileMode.CLAMP); } else { mShadowLinearGradient = new LinearGradient(0, 0, 0, SHADING_FACTOR, Color.BLACK, Color.TRANSPARENT, TileMode.CLAMP); } //折叠线附近的效果 mGradientShadow.setStyle(Style.FILL); mGradientShadow.setShader(mShadowLinearGradient); mShadowGradientMatrix = new Matrix(); mAnchorFactor = anchorFactor; mNumberOfFolds = numberOfFolds; mOriginalWidth = getMeasuredWidth(); Log.i(" mOriginalWidth", mOriginalWidth+""); mOriginalHeight = getMeasuredHeight(); Log.i(" mOriginalHeight", mOriginalHeight+""); mFoldRectArray = new Rect[mNumberOfFolds]; mMatrix = new Matrix [mNumberOfFolds]; for (int x = 0; x < mNumberOfFolds; x++) { mMatrix[x] = new Matrix(); } int h = mOriginalHeight; int w = mOriginalWidth; if (FoldingLayoutActivity.IS_JBMR2) { mFullBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(mFullBitmap); getChildAt(0).draw(canvas); Log.i("IS_JBMR2", FoldingLayoutActivity.IS_JBMR2+""); } //折叠成几个部分,每隔部分的宽 int delta = Math.round(mIsHorizontal ? ((float) w) / ((float) mNumberOfFolds) : ((float) h) /((float) mNumberOfFolds)); Log.i("delta", delta+""); /* Loops through the number of folds and segments the full layout into a number * of smaller equal components. If the number of folds is odd, then one of the * components will be smaller than all the rest. Note that deltap below handles * the calculation for an odd number of folds.*/ for (int x = 0; x < mNumberOfFolds; x++) { if (mIsHorizontal) { int deltap = (x + 1) * delta > w ? w - x * delta : delta; Log.i("deltap", x+"x"+deltap+""); mFoldRectArray[x] = new Rect(x * delta, 0, x * delta + deltap, h); } else { int deltap = (x + 1) * delta > h ? h - x * delta : delta; mFoldRectArray[x] = new Rect(0, x * delta, w, x * delta + deltap); } } if (mIsHorizontal) { mFoldMaxHeight = h; mFoldMaxWidth = delta; } else { mFoldMaxHeight = delta; mFoldMaxWidth = w; } //设置已经准备就绪 mIsFoldPrepared = true; }
这个方法从名字就可以看出来是用于数据初始化,开始时初始化setPolyToPoly方法参数数组,然后设置了画笔、折叠方向、折叠线附近的折叠效果灯。最重要的就是初始化这两个参数: private Rect[] mFoldRectArray:
private Matrix [] mMatrix: 他们分别根据折叠的数目来赋值。
2、calculateMatrices() 这个方法是计算的核心部分,
/* * Calculates the transformation matrices used to draw each of the separate * folding segments from this view. */ private void calculateMatrices() { // 设置可以绘图了 mShouldDraw = true; // 没有准备好 返回 if (! mIsFoldPrepared) { return; } /** * If the fold factor is 1 than the folding view should not be seen and * the canvas can be left completely empty. */ if ( mFoldFactor == 1) { // 已经完全折叠了不需要绘制了 mShouldDraw = false; return; } // 折叠结束 if ( mFoldFactor == 0 && mPreviousFoldFactor > 0) { mFoldListener.onEndFold(); } // 折叠开始 if ( mPreviousFoldFactor == 0 && mFoldFactor > 0) { mFoldListener.onStartFold(); } // 已经折叠了,将当前的折叠因子赋值到已经折叠过的折叠因子 mPreviousFoldFactor = mFoldFactor; /* * Reset all the transformation matrices back to identity before * computing the new transformation */ for ( int x = 0; x < mNumberOfFolds; x++) { mMatrix[x].reset(); } //根据折叠因子来得到相对系数 float cTranslationFactor = 1 - mFoldFactor; Log. i("cTranslationFactor" , cTranslationFactor + "" ); //计算整体变换的距离 float translatedDistance = mIsHorizontal ? mOriginalWidth * cTranslationFactor : mOriginalHeight * cTranslationFactor; Log. i("translatedDistance" , translatedDistance + "" ); //得到每一个折叠视图变换的移动距离 float translatedDistancePerFold = Math.round(translatedDistance / mNumberOfFolds); Log. i("translatedDistancePerFold" , translatedDistancePerFold + "" ); /* * For an odd number of folds, the rounding error may cause the * translatedDistancePerFold to be grater than the max fold width or * height. */ Log. i("mFoldMaxWidth" , mFoldMaxWidth + "" ); //计算出被画视图的宽度 mFoldDrawWidth = mFoldMaxWidth < translatedDistancePerFold ? translatedDistancePerFold : mFoldMaxWidth; Log. i("mFoldDrawWidth" , mFoldDrawWidth + "" ); //计算出被画视图的高度 mFoldDrawHeight = mFoldMaxHeight < translatedDistancePerFold ? translatedDistancePerFold : mFoldMaxHeight; Log. i("mFoldDrawHeight" , mFoldDrawHeight + "" ); float translatedDistanceFoldSquared = translatedDistancePerFold * translatedDistancePerFold; Log. i("translatedDistanceFoldSquared" , translatedDistanceFoldSquared + ""); /* * Calculate the depth of the fold into the screen using pythagorean * theorem. * 根据勾股定理计算出折叠效果的深度 */ float depth = mIsHorizontal ? ( float) Math . sqrt((double) (mFoldDrawWidth * mFoldDrawWidth - translatedDistanceFoldSquared)) : ( float) Math . sqrt((double) (mFoldDrawHeight * mFoldDrawHeight - translatedDistanceFoldSquared)); Log. i("depth" , depth + "" ); /* * The size of some object is always inversely proportional to the * distance it is away from the viewpoint. The constant can be varied to * to affect the amount of perspective. * 这个地方又使用了一个因子,它是根据深度depth来得到的。主要也是用来计算相对的值,体现在高度上。 */ float scaleFactor = DEPTH_CONSTANT / ( DEPTH_CONSTANT + depth); Log. i("scaleFactor" , scaleFactor + "" ); float scaledWidth, scaledHeight, bottomScaledPoint, topScaledPoint, rightScaledPoint, leftScaledPoint; if ( mIsHorizontal) { //得到变化后的宽度,其实和translatedDistance相差无几 scaledWidth = mFoldDrawWidth * cTranslationFactor; Log. i("scaledWidth" , scaledWidth + "" ); //得到变化后的高度 scaledHeight = mFoldDrawHeight * scaleFactor; Log. i("scaledHeight" , scaledHeight + "" ); } else { scaledWidth = mFoldDrawWidth * scaleFactor; scaledHeight = mFoldDrawHeight * cTranslationFactor; } //根据变化后的高度和宽度来计算出坐标点 topScaledPoint = ( mFoldDrawHeight - scaledHeight) / 2.0f; bottomScaledPoint = topScaledPoint + scaledHeight; leftScaledPoint = ( mFoldDrawWidth - scaledWidth) / 2.0f; rightScaledPoint = leftScaledPoint + scaledWidth; //锚点计算 float anchorPoint = mIsHorizontal ? mAnchorFactor * mOriginalWidth : mAnchorFactor * mOriginalHeight; Log. i("anchorPoint" , anchorPoint + "" ); /* The fold along which the anchor point is located. */ float midFold = mIsHorizontal ? (anchorPoint / mFoldDrawWidth) : anchorPoint / mFoldDrawHeight; //一下的坐标大家一定很熟悉,就是之前介绍的方法的参数 表示单个折叠视图 mSrc[0] = 0; mSrc[1] = 0; mSrc[2] = 0; mSrc[3] = mFoldDrawHeight; mSrc[4] = mFoldDrawWidth; mSrc[5] = 0; mSrc[6] = mFoldDrawWidth; mSrc[7] = mFoldDrawHeight; /* * Computes the transformation matrix for each fold using the values * calculated above. */ for ( int x = 0; x < mNumberOfFolds; x++) { boolean isEven = (x % 2 == 0); if ( mIsHorizontal) { mDst[0] = (anchorPoint > x * mFoldDrawWidth) ? anchorPoint + (x - midFold) * scaledWidth : anchorPoint - (midFold - x) * scaledWidth; Log. i("mDst[0]" , mDst [0] + "" ); mDst[1] = isEven ? 0 : topScaledPoint; Log. i("mDst[1]" , mDst [1] + "" ); mDst[2] = mDst[0]; Log. i("mDst[2]" , mDst [2] + "" ); mDst[3] = isEven ? mFoldDrawHeight : bottomScaledPoint; Log. i("mDst[3]" , mDst [4] + "" ); mDst[4] = (anchorPoint > (x + 1) * mFoldDrawWidth) ? anchorPoint + (x + 1 - midFold) * scaledWidth : anchorPoint - (midFold - x - 1) * scaledWidth; Log. i("mDst[4]" , mDst [4] + "" ); mDst[5] = isEven ? topScaledPoint : 0; Log. i("mDst[5]" , mDst [5] + "" ); mDst[6] = mDst[4]; Log. i("mDst[6]" , mDst [6] + "" ); mDst[7] = isEven ? bottomScaledPoint : mFoldDrawHeight; Log. i("mDst[7]" , mDst [7] + "" ); } else { mDst[0] = isEven ? 0 : leftScaledPoint; mDst[1] = (anchorPoint > x * mFoldDrawHeight) ? anchorPoint + (x - midFold) * scaledHeight : anchorPoint - (midFold - x) * scaledHeight; mDst[2] = isEven ? leftScaledPoint : 0; mDst[3] = (anchorPoint > (x + 1) * mFoldDrawHeight) ? anchorPoint + (x + 1 - midFold) * scaledHeight : anchorPoint - (midFold - x - 1) * scaledHeight; mDst[4] = isEven ? mFoldDrawWidth : rightScaledPoint; mDst[5] = mDst[1]; mDst[6] = isEven ? rightScaledPoint : mFoldDrawWidth; mDst[7] = mDst[3]; } /* * Pixel fractions are present for odd number of folds which need to * be rounded off here. */ for ( int y = 0; y < 8; y++) { mDst[y] = Math. round(mDst[y]); } /* * If it so happens that any of the folds have reached a point where * the width or height of that fold is 0, then nothing needs to be * drawn onto the canvas because the view is essentially completely * folded. */ if ( mIsHorizontal) { if ( mDst[4] <= mDst[0] || mDst[6] <= mDst[2]) { mShouldDraw = false; return; } } else { if ( mDst[3] <= mDst[1] || mDst[7] <= mDst[5]) { mShouldDraw = false; return; } } /* Sets the shadow and bitmap transformation matrices. */ mMatrix[x].setPolyToPoly( mSrc, 0, mDst, 0, NUM_OF_POLY_POINTS / 2); } /* * The shadows on the folds are split into two parts: Solid shadows and * gradients. Every other fold has a solid shadow which overlays the * whole fold. Similarly, the folds in between these alternating folds * also have an overlaying shadow. However, it is a gradient that takes * up part of the fold as opposed to a solid shadow overlaying the whole * fold. */ /* Solid shadow paint object. */ int alpha = ( int) ( mFoldFactor * 255 * SHADING_ALPHA); mSolidShadow.setColor(Color. argb(alpha, 0, 0, 0)); if ( mIsHorizontal) { mShadowGradientMatrix.setScale( mFoldDrawWidth, 1); mShadowLinearGradient.setLocalMatrix( mShadowGradientMatrix); } else { mShadowGradientMatrix.setScale(1, mFoldDrawHeight); mShadowLinearGradient.setLocalMatrix( mShadowGradientMatrix); } mGradientShadow.setAlpha(alpha); }
首先根据mFoldFactor得到cTranslationFactor,mFoldFactor是根据手指滑动的距离得到的,是一个很小的数值,如果滑动距离小的话每次只有0.0几的变化。所以得到的cTranslationFactor就像是一个百分比一样,来计算相对的数值。然后根据cTranslationFactor计算出translatedDistance、mFoldDrawWidth、depth等。详细说明已经在代码中注释了,在此就不细说了,如图:
3、dispatchDraw(Canvas canvas) 最后就是把图形绘制出来,这个方法在之前的自定义ViewGroup博客中已经提到过,如果不重写这个方法,那上面的工作室无法显示出来的。看代码:
@Override protected void dispatchDraw(Canvas canvas) { /** * If prepareFold has not been called or if preparation has not * completed yet, then no custom drawing will take place so only need to * invoke super's onDraw and return. */ if (! mIsFoldPrepared || mFoldFactor == 0) { super.dispatchDraw(canvas); return; } if (! mShouldDraw) { return; } Rect src; /* * Draws the bitmaps and shadows on the canvas with the appropriate * transformations. */ for ( int x = 0; x < mNumberOfFolds; x++) { src = mFoldRectArray[x]; /* The canvas is saved and restored for every individual fold */ canvas.save(); /* * Concatenates the canvas with the transformation matrix for the * the segment of the view corresponding to the actual image being * displayed. */ canvas.concat( mMatrix[x]); if (FoldingLayoutActivity. IS_JBMR2) { mDstRect.set(0, 0, src.width(), src.height()); canvas.drawBitmap( mFullBitmap, src, mDstRect, null); } else { /* * The same transformation matrix is used for both the shadow * and the image segment. The canvas is clipped to account for * the size of each fold and is translated so they are drawn in * the right place. The shadow is then drawn on top of the * different folds using the sametransformation matrix. */ canvas.clipRect(0, 0, src. right - src.left, src. bottom - src. top); if ( mIsHorizontal) { canvas.translate(-src. left, 0); } else { canvas.translate(0, -src. top); } super.dispatchDraw(canvas); if ( mIsHorizontal) { canvas.translate(src. left, 0); } else { canvas.translate(0, src. top); } } /* Draws the shadows corresponding to this specific fold. */ if (x % 2 == 0) { canvas.drawRect(0, 0, mFoldDrawWidth, mFoldDrawHeight, mSolidShadow); } else { canvas.drawRect(0, 0, mFoldDrawWidth, mFoldDrawHeight, mGradientShadow); } canvas.restore(); } }
其实这个方法最主要的两个地方就是,canvas.concat( mMatrix[x])和canvas.clipRect(0, 0, src. right - src.left, src. bottom- src. top)。首先在最外层是一个for循环,这个循环根据折叠的数目迭代,本例中这个数值为2,所以将迭代两次。接着从mFoldRectArray数组中取出一个Rect赋值给src,然后就调用canvas.concat( mMatrix[x])方法了。我们知道,这个Matrix刚才已经使用setPolyToPoly方法进行变换了,那如何将变换后的矩阵的效果应用到视图上呢,就是使用canvas.concat( mMatrix[x])方法,如果没有这个方法,那就不会看到折叠的效果。然后就是canvas.clipRect(0, 0, src.right - src.left, src.bottom - src.top)方法了,这个方法用于对图形进行裁剪,显示出每一个折叠的视图。需要注意的地方是这个方法要在后续画画操作之前进行剪切才能生效,这也就是为什么这个方法在super.dispatchDraw(canvas)之前调用。接着还需要对视图进行平移操作,canvas.translate(-src.left, 0),不然的话会显示两个相同的图片。
- development-master-samples-devbytes-graphics-FoldingLayout.tar.gz (279.8 KB)
- 下载次数: 1
发表评论
-
带你深入理解 FLUTTER 中的字体“冷”知识
2020-08-10 23:40 612本篇将带你深入理解 Flutter 开发过程中关于字体和文 ... -
Flutter -自定义日历组件
2020-03-01 17:56 1087颜色文件和屏幕适配的文件 可以自己给定 import ... -
Dart高级(一)——泛型与Json To Bean
2020-02-23 19:13 976从 Flutter 发布到现在, 越来越多人开始尝试使用 Da ... -
flutter loading、Progress进度条
2020-02-21 17:03 1132Flutter Progress 1 条形无固定值进度条 ... -
Flutter使用Https加载图片
2020-02-21 01:39 983Flutter使用Https加载图片 使用http加载图片出 ... -
flutter shared_preferences 异步变同步
2020-02-21 00:55 827前言 引用 在开发原生iOS或Native应用时,一般有判断上 ... -
Flutter TextField边框颜色
2020-02-19 21:31 911监听要销毁 myController.dispose(); T ... -
flutter Future的正确用法
2020-02-18 21:55 787在flutter中经常会用到异步任务,dart中异步任务异步处 ... -
记一次Flutter简单粗暴处理HTTPS证书检验方法
2020-02-18 14:13 921最近在做Flutter项目到了遇到一个无解的事情,当使用Ima ... -
flutter 获取屏幕宽度高度 通知栏高度等屏幕信息
2019-07-27 08:39 1306##MediaQuery MediaQuery.of(con ... -
关于flutter RefreshIndicator扩展listview下拉刷新的问题
2019-07-10 19:40 1058当条目过少时listview某些嵌套情况下可能不会滚动(条目 ... -
flutter listview 改变状态的时候一直无限添加
2019-07-10 16:01 721setstate的时候会一直无限的调用listview.bui ... -
Flutter Android端启动白屏问题的解决
2019-07-09 00:51 1478问题描述 Flutter 应用在 Android 端上启动时 ... -
Flutter中SnackBar使用
2019-07-08 23:43 742底部弹出,然后在指定时间后消失。 注意: build(Bui ... -
Flutter 之点击空白区域收起键盘
2019-07-08 18:43 1758点击空白处取消TextField焦点这个需求是非常简单的,在学 ... -
Flutter 弹窗 Dialog ,AlertDialog,IOS风格
2019-07-08 18:04 1349import 'package:flutter/mate ... -
flutter ---TextField 之 输入类型、长度限制
2019-07-08 14:30 2288TextField想要实现输入类型、长度限制需要先引入impo ... -
【flutter 溢出BUG】键盘上显示bottom overflowed by 104 PIXELS
2019-07-08 11:13 1516一开始直接使用Scaffold布局,body:new Colu ... -
解决Flutter项目卡在Initializing gradle...界面的问题
2019-07-07 12:53 842Flutter最近很火,我抽出了一点时间对Flutter进行了 ... -
关于android O 上 NotificationChannel 的一些注意事项
2019-07-04 11:47 921最近在适配android O,遇到个问题,应用中原本有设置界面 ...
相关推荐
1. 层次结构:树视图控件的核心特性是能够展示数据的层级关系,用户可以通过展开和折叠节点来查看不同级别的信息。 2. 节点操作:支持添加、删除、移动和重命名节点,满足动态更新数据的需求。 3. 图标和文本:每个...
在C#编程中,树视图控件(TreeView)是一种常用的数据展示工具,它能够以层级结构显示数据,便于用户浏览和操作。本篇将深入探讨如何在C#环境中使用树视图控件,包括如何添加节点、为节点添加子节点,以及相关的编程...
其中,"树控件"(CTreeCtrl)和"视图控件"(CView)是两种特别重要的控件类型。 1. **树控件(CTreeCtrl)**: 树控件通常用于显示层次结构的数据,例如文件系统目录、组织结构等。它包含一个或多个可展开/折叠的节点...
### 树型视图控件详解 #### 一、引言 树型视图控件是一种常见的用户界面组件,主要用于展示具有层级结构的数据。本文档将深入探讨树型视图控件的基本概念、创建方法、样式设置以及常用操作,帮助读者理解和掌握其...
在Visual C++编程环境中,树形视图控件(TreeView Control)是一种常用的数据展示组件,它以层次结构的形式展示信息,常用于文件系统、目录结构或者复杂的对象关系表示。本教程将详细介绍如何在VC++中使用树形视图...
然而,原生的DataGridView控件并不直接支持折叠功能。"可折叠datagridview控件"是指通过自定义扩展或第三方库实现的,能够在数据显示时进行折叠和展开操作的特殊版本。这种控件能够帮助用户更有效地管理和查看大量...
在C#编程中,树视图控件(TreeView)是一种常用的数据展示工具,它能够以层级结构显示数据,常用于文件系统浏览、组织结构展示等场景。本教程将深入讲解如何利用C#来操作和定制树视图控件,帮助开发者更好地理解和...
在.NET Framework中,Visual C# 2003提供了一个强大的控件库,其中包括树视图控件(TreeView)。这个控件通常用于显示层次结构的数据,例如文件系统、组织结构或者菜单。本教程将深入探讨如何在VC# 2003中有效地使用...
在C# Windows Forms开发中,树视图控件(TreeView)是一种常见的用户界面元素,用于显示数据的层次结构。这个控件通常用于展示文件系统、组织结构或自定义的分层数据。本教程将深入讲解如何在C#中有效地使用Treeview...
在属性窗口中,可以启用Has Buttons和Has Lines选项,这将使树视图控件看起来像Windows资源管理器那样,有展开/折叠按钮和连接线。 3. **创建自定义类**: 为了方便扩展功能,通常会从CTreeCtrl派生一个新的类,...
在Windows Forms(Winform)开发中,折叠导航控件是一种常用的设计元素,它能帮助用户以组织良好的方式浏览和操作应用程序的各种功能。"CodeProject winform折叠导航控件"是针对这种需求的一个开源解决方案,源自...
本资源提供的"C# winform 可折叠的菜单控件"是一个专门针对WinForm设计的自定义菜单导航解决方案,它不仅提供了基本的菜单功能,还具有可折叠的特性,使得用户界面更加灵活和友好。 1. **DockPanel控件**:这是.NET...
本教程将深入探讨如何创建一个“漂亮MSN风格的垂直折叠菜单控件”,它将为你的应用程序增添现代、直观且吸引人的导航体验。这个控件的设计灵感来源于微软的MSN,其特点是简洁、优雅且具有良好的交互性。 首先,我们...
在VC++环境中,MFC(Microsoft Foundation Classes)库为我们提供了丰富的控件来构建用户界面,其中包括树形视图控件(CTreeViewCtrl)。本项目主要探讨如何在VC平台上利用MFC扩展这一控件,实现一个结合了复选框...
一个可折叠的VC++自定义列表控件 一款VC++自定义的列表控件,可以展开、折叠。 WINDOWS应用程序的创建一般都需要使用控件,像VB、DELPHI等编程环境都提供了相当多的控件供程序员调用,这些控件基本上能满足程序...
1. **树列表视图控件的设计原理**:理解如何将树形结构与表格数据结合,如何处理节点的展开、折叠、排序和筛选等操作。 2. **CColumnTreeCtrl类的结构**:查看源代码,学习类的成员函数和数据结构,了解其如何扩展...
3.刚打开的时候,可能会提示加载控件出错误,忽略错误,直接运行就可以了,因为原本两个项目本是放在一个解决方案下的,后来我分离了,控件代码没有问题,只是调用端时添加引用的事 注:本控件是我参考网上资料,...
`TreeView`控件主要用于展示层次结构的数据,每个节点代表一个条目,节点之间可以有父子关系,通过展开和折叠节点来显示或隐藏子节点。而`ListView`控件则提供了一种表格形式的数据展示方式,可以显示多列数据,每行...