java扇面渐变色如何实现,CorePlex开发手记:一、Winform窗体皮肤及简单换肤机制...

前言: CorePlex代码库 作为一个Visual Studio插件, 允许用户通过VS直接访问在线代码库。开发过程中我翻阅了很多网上的资料,也总结了一些技术要点,现写成系列文章,以飨读者。同时,里面某些技术也是我第一次使用,如有不对的地方,还请行家狠拍,欢迎大家指正~

闲话休絮,进入正题。从本篇文章开始,介绍 CorePlex 的窗体皮肤机制,以及简单的换肤功能。我们先来看看效果:

85edd986fb88a181332cea34c01c91ad.png

换一个皮肤看看:

1e2d8b7a2b3784e40052708b3f2f3f29.png

需要实现的是圆角窗体+四周的阴影,要实现这个,大致的思路是这样的:先使用 Graphics 绘制一个 Bitmap,将需要的皮肤绘制成一个内存图,然后使用 Win32的API:UpdateLayeredWindow 将这个构造好的 Bitmap 绘制/更新到窗体上。我们来看看具体的实现吧。

第一部分,构造皮肤背景。

为了实现圆角以及四周的阴影,我将窗体背景划分成了九宫格的形式:

84a620370f331a44c79a79ca722a8f71.png

主要思路是:除 5 之外的其他部分,都作为窗体的边框和圆角来处理。而5这个部分,则作为圆角窗体的主体背景部分。1、3、7、9四个部分,作为圆角,我使用 PathGradientBrush来绘制扇形渐变,而2、4、6、8四个部分,作为边框,我使用 LinearGradientBrush来绘制线性渐变。

不多说,见代码:

///

///绘制四角的阴影///

///

/// 圆角区域正方形的大小

/// private voidDrawCorners(Graphicsg, SizecorSize)

{

/*

* 四个角,每个角都是一个扇面

* 画图时扇面由外弧、内弧以及两段的连接线构成图形

* 然后在内弧中间附近向外做渐变

*

* 阴影分为9宫格,5为内部背景图部分

* 1 2 3

* 4 5 6

* 7 8 9

*/Action DrawCorenerN = (n) =>

{

using(GraphicsPathgp = newGraphicsPath())

{

// 扇面外沿、内沿曲线的尺寸SizesizeOutSide = newSize(corSize.Width * 2, corSize.Height * 2);

SizesizeInSide = newSize(this.SkinOptions.CornerRadius * 2, this.SkinOptions.CornerRadius * 2);

// 扇面外沿、内沿曲线的位置PointlocationOutSide, locationInSide;

// 当圆角半径小于MinCornerRadius时,内沿不绘制曲线,而以线段绘制近似值。该线段绘制方向是从p1指向p2。Pointp1, p2;

// 渐变起点位置PointFbrushCenter;

// 扇面起始角度floatstartAngle;

// 根据四个方位不同,确定扇面的位置、角度及渐变起点位置switch(n)

{

case1:

locationOutSide = newPoint(0, 0);

startAngle = 180;

brushCenter = newPointF((float)sizeOutSide.Width - sizeInSide.Width * 0.5f,

(float)sizeOutSide.Height - sizeInSide.Height * 0.5f);

p1 = newPoint(corSize.Width, this.SkinOptions.ShadowWidth);

p2 = newPoint(this.SkinOptions.ShadowWidth, corSize.Height);

break;

case3:

locationOutSide = newPoint(this.Width - sizeOutSide.Width, 0);

startAngle = 270;

brushCenter = newPointF((float)locationOutSide.X + sizeInSide.Width * 0.5f,

(float)sizeOutSide.Height - sizeInSide.Height * 0.5f);

p1 = newPoint(this.Width - this.SkinOptions.ShadowWidth, corSize.Height);

p2 = newPoint(this.Width - corSize.Width, this.SkinOptions.ShadowWidth);

break;

case7:

locationOutSide = newPoint(0, this.Height - sizeOutSide.Height);

startAngle = 90;

brushCenter = newPointF((float)sizeOutSide.Width - sizeInSide.Width * 0.5f,

(float)locationOutSide.Y + sizeInSide.Height * 0.5f);

p1 = newPoint(this.SkinOptions.ShadowWidth, this.Height - corSize.Height);

p2 = newPoint(corSize.Width, this.Height - this.SkinOptions.ShadowWidth);

break;

default:

locationOutSide = newPoint(this.Width - sizeOutSide.Width, this.Height - sizeOutSide.Height);

startAngle = 0;

brushCenter = newPointF((float)locationOutSide.X + sizeInSide.Width * 0.5f,

(float)locationOutSide.Y + sizeInSide.Height * 0.5f);

p1 = newPoint(this.Width - corSize.Width, this.Height - this.SkinOptions.ShadowWidth);

p2 = newPoint(this.Width - this.SkinOptions.ShadowWidth, this.Height - corSize.Height);

break;

}

// 扇面外沿曲线RectanglerecOutSide = newRectangle(locationOutSide, sizeOutSide);

// 扇面内沿曲线的位置locationInSide = newPoint(locationOutSide.X + (sizeOutSide.Width - sizeInSide.Width) / 2,

locationOutSide.Y + (sizeOutSide.Height - sizeInSide.Height) / 2);

// 扇面内沿曲线RectanglerecInSide = newRectangle(locationInSide, sizeInSide);

// 将扇面添加到形状,以备绘制gp.AddArc(recOutSide, startAngle, 91);

if(this.SkinOptions.CornerRadius > MinCornerRadius)

gp.AddArc(recInSide, startAngle + 90, -91);

elsegp.AddLine(p1, p2);

// 使用渐变笔刷using(PathGradientBrushshadowBrush = newPathGradientBrush(gp))

{

Color[] colors = newColor[2];

float[] positions = new float[2];

ColorBlendsBlend = newColorBlend();

// 扇面外沿色colors[0] = this.SkinOptions.CornerColor[1];

// 扇面内沿色colors[1] = this.SkinOptions.CornerColor[0];

positions[0] = 0.0f;

positions[1] = 1.0f;

sBlend.Colors = colors;

sBlend.Positions = positions;

shadowBrush.InterpolationColors = sBlend;

// 上色中心点shadowBrush.CenterPoint = brushCenter;

g.FillPath(shadowBrush, gp);

}

}

};

DrawCorenerN(1);

DrawCorenerN(3);

DrawCorenerN(7);

DrawCorenerN(9);

}

///

///绘制上下左右四边的阴影///

///

///

///

/// private voidDrawLines(Graphicsg, SizecorSize, SizegradientSize_LR, SizegradientSize_TB)

{

Rectanglerect2 = newRectangle(newPoint(corSize.Width, 0), gradientSize_TB);

Rectanglerect4 = newRectangle(newPoint(0, corSize.Width), gradientSize_LR);

Rectanglerect6 = newRectangle(newPoint(this.Size.Width - this.SkinOptions.ShadowWidth, corSize.Width), gradientSize_LR);

Rectanglerect8 = newRectangle(newPoint(corSize.Width, this.Size.Height - this.SkinOptions.ShadowWidth), gradientSize_TB);

using(

LinearGradientBrushbrush2 = newLinearGradientBrush(rect2, this.SkinOptions.ShadowColor[1],

this.SkinOptions.ShadowColor[0], LinearGradientMode.Vertical),

brush4 = newLinearGradientBrush(rect4, this.SkinOptions.ShadowColor[1],

this.SkinOptions.ShadowColor[0], LinearGradientMode.Horizontal),

brush6 = newLinearGradientBrush(rect6, this.SkinOptions.ShadowColor[0],

this.SkinOptions.ShadowColor[1], LinearGradientMode.Horizontal),

brush8 = newLinearGradientBrush(rect8, this.SkinOptions.ShadowColor[0],

this.SkinOptions.ShadowColor[1], LinearGradientMode.Vertical)

)

{

g.FillRectangle(brush2, rect2);

g.FillRectangle(brush4, rect4);

g.FillRectangle(brush6, rect6);

g.FillRectangle(brush8, rect8);

}

}

好,到此为止,四周的圆角和渐变边框算是模拟出来了,然后就是将 5号为止的主背景绘制上去,以及添加一些高光、暗部等线条,突出质感:

///

///绘制主背景图///

/// private voidDrawMain(Graphicsg)

{

// 要显示的区域图像的大小RectangledestRect = newRectangle(0, 0,

this.Width - this.SkinOptions.ShadowWidth * 2 + 1,

this.Height - this.SkinOptions.ShadowWidth * 2 + 1);

// 建立一个临时的 bitmap,用于存放被圆角化的图像using(BitmapcorBg = newBitmap(destRect.Width, destRect.Height))

{

using(GraphicscorG = Graphics.FromImage(corBg))

{

corG.SmoothingMode = SmoothingMode.HighQuality;

// 创建圆角区域using(GraphicsPathgp = CreateRoundRect(destRect, this.SkinOptions.CornerRadius))

{

Regionre = newSystem.Drawing.Region(gp);

// 设置画布区域为圆角区域corG.IntersectClip(re);

Penp = newPen(this.SkinOptions.BorderColor);

p.Width = this.SkinOptions.BorderWidth;

p.Alignment = PenAlignment.Inset;

switch(this.SkinOptions.BackgroundLayout)

{

caseImageLayout.Center:

{

// 创建源图上的截取区域RectanglesrcRect = newRectangle(newPoint(0, 0), this.SkinOptions.BackgroundImage.Size);

// 绘制背景图corG.DrawImage(this.SkinOptions.BackgroundImage,

(this.Width - this.SkinOptions.BackgroundImage.Width) / 2,

(this.Height - this.SkinOptions.BackgroundImage.Height) / 2,

srcRect, GraphicsUnit.Pixel);

}

break;

caseImageLayout.Stretch:

{

// 创建源图上的截取区域RectanglesrcRect = newRectangle(newPoint(0, 0), this.SkinOptions.BackgroundImage.Size);

// 绘制背景图corG.DrawImage(this.SkinOptions.BackgroundImage, p.Width, p.Width, srcRect, GraphicsUnit.Pixel);

}

break;

caseImageLayout.Tile:

{

// 创建源图上的截取区域TextureBrushtb = newTextureBrush(this.SkinOptions.BackgroundImage);

corG.FillRectangle(tb, destRect);

}

break;

caseImageLayout.Zoom:

{

// 创′建¨源′图?上?的?截?取?区?域òRectanglesrcRect = newRectangle(newPoint(0, 0), this.SkinOptions.BackgroundImage.Size);

// 绘制背景图corG.DrawImage(this.SkinOptions.BackgroundImage,

(this.Width - this.SkinOptions.BackgroundImage.Width) / 2,

(this.Height - this.SkinOptions.BackgroundImage.Height) / 2,

srcRect, GraphicsUnit.Pixel);

}

break;

caseImageLayout.None:

default:

corG.DrawImage(this.SkinOptions.BackgroundImage, p.Width, p.Width);

break;

}

// 构造外边框RectangleborderOut = newRectangle(0, 0, destRect.Width - 1, destRect.Height - 1);

// 绘制外边框corG.DrawPath(p, CreateRoundRect(borderOut, this.SkinOptions.CornerRadius));

// 构造内边框RectangleborderIn = newRectangle(1, 1, borderOut.Width - 2, borderOut.Height - 2);

using(LinearGradientBrushb = newLinearGradientBrush(borderIn,

this.SkinOptions.BorderHighlightColor[0],

this.SkinOptions.BorderHighlightColor[1],

this.SkinOptions.BorderHighlightAngle))

{

// 绘制内边框高光using(PenlightPen = newPen(b))

{

// 绘制内边框corG.DrawPath(lightPen, CreateRoundRect(borderIn, this.SkinOptions.CornerRadius));

}

}

// 将圆角图绘制到主画布g.DrawImage(corBg, this.SkinOptions.ShadowWidth, this.SkinOptions.ShadowWidth);

#region绘制Logo

using(Bitmaplogo = Resources.Default.formlogo)

{

RectanglepaintTo = newRectangle(25, 20, logo.Width, logo.Height);

RectanglesourceRec = newRectangle(0, 0, logo.Width, logo.Height);

g.DrawImage(logo, paintTo, sourceRec, GraphicsUnit.Pixel);

}

#endregion

#region绘制控制按钮

using(Bitmapcb = Resources.Default.controlboxes)

{

RectanglepaintTo = newRectangle(this.Width - this.SkinOptions.ShadowWidth - cb.Width + 3,

this.SkinOptions.ShadowWidth - 1, cb.Width, ControlBoxHeight);

RectanglesourceRec = newRectangle(0, this._currentControlBoxImgY, cb.Width, ControlBoxHeight);

g.DrawImage(cb, paintTo, sourceRec, GraphicsUnit.Pixel);

}

#endregion}

}

}

}

///

///构造圆角路径///

///

///

/// privateGraphicsPathCreateRoundRect(Rectanglerect, intradius)

{

GraphicsPathgp = newGraphicsPath();

intx = rect.X;

inty = rect.Y;

intwidth = rect.Width;

intheight = rect.Height;

if(width > 0 && height > 0)

{

// 半径大才做圆角if(radius > MinCornerRadius)

{

radius = Math.Min(radius, height / 2 - 1);

radius = Math.Min(radius, width / 2 - 1);

gp.AddArc(x + width - (radius * 2), y, radius * 2, radius * 2, 270, 90);

gp.AddLine(x + width, y + radius, x + width, y + height - (radius * 2));

gp.AddArc(x + width - (radius * 2), y + height - (radius * 2), radius * 2, radius * 2, 0, 90);

gp.AddLine(x + width - (radius * 2), y + height, x + radius, y + height);

gp.AddArc(x, y + height - (radius * 2), radius * 2, radius * 2, 90, 90);

gp.AddLine(x, y + height - (radius * 2), x, y + radius);

gp.AddArc(x, y, radius * 2, radius * 2, 180, 90);

}

else{

gp.AddLine(x + width - radius, y, x + width, y + radius);

gp.AddLine(x + width, y + radius, x + width, y + height - radius);

gp.AddLine(x + width, y + height - radius, x + width - radius, y + height);

gp.AddLine(x + width - radius, y + height, x + radius, y + height);

gp.AddLine(x + radius, y + height, x, y + height - radius);

gp.AddLine(x, y + height - radius, x, y + radius);

gp.AddLine(x, y + radius, x + radius, y);

}

gp.CloseFigure();

}

returngp;

}

好了,带圆角、渐变阴影的窗体背景算是构造好了,但它现在仅仅是一张内存里的 bitmap 图片,我们要如何才能将它显示到窗体上呢?

第二部分:使用 Win32的API将 bitmap 更新到窗体

这个操作使用 UpdateLayeredWindow 来进行,MSDN上是这样描述的:Updates the position, size, shape, content, and translucency of a layered window.

现在我们只是用它来将我们绘制好的内存图Update到一个窗体上。你应该已经注意到MSDN的说明以及这个方法的名字了,单词Window前有一个限定词,Layered。那么我们怎么构造一个Layered的Window呢?或者说,怎么才能使我们的窗体成为Layered的呢?很简单,你可以这样:

protected overrideCreateParamsCreateParams

{

get{

CreateParamscp = base.CreateParams;

// 绘制背景必须针对具有 WS_EX_LAYERED 扩展风格的窗体进行if(!this.DesignMode)

{

cp.ExStyle |= Win32.WS_EX_LAYERED; // WS_EX_LAYERED}

returncp;

}

}

好了,我们有一个合适的Window了,下面我们使用 UpdateLayeredWindow 来讲绘制的内存图更新到窗体上:(老实说,这段代码是从网上Copy的,嘿嘿~)

///

///绘制已构造好的位图///

///

/// private voidSetBitmap(Bitmapbitmap, byteopacity = 255)

{

if(bitmap.PixelFormat != PixelFormat.Format32bppArgb)

throw newException("窗体背景图必须是 8 位/通道 RGB 颜色的图。");

// The ideia of this is very simple,

// 1. Create a compatible DC with screen;

// 2. Select the bitmap with 32bpp with alpha-channel in the compatible DC;

// 3. Call the UpdateLayeredWindow.IntPtrscreenDc = Win32.GetDC(IntPtr.Zero);

IntPtrmemDc = Win32.CreateCompatibleDC(screenDc);

IntPtrhBitmap = IntPtr.Zero;

IntPtroldBitmap = IntPtr.Zero;

try{

hBitmap = bitmap.GetHbitmap(Color.FromArgb(0)); // grab a GDI handle from this GDI+ bitmapoldBitmap = Win32.SelectObject(memDc, hBitmap);

Win32.Sizesize = newWin32.Size(bitmap.Width, bitmap.Height);

Win32.PointpointSource = newWin32.Point(0, 0);

Win32.PointtopPos = newWin32.Point(Left, Top);

Win32.BLENDFUNCTIONblend = newWin32.BLENDFUNCTION();

blend.BlendOp = Win32.AC_SRC_OVER;

blend.BlendFlags = 0;

blend.SourceConstantAlpha = opacity;

blend.AlphaFormat = Win32.AC_SRC_ALPHA;

Win32.UpdateLayeredWindow(Handle, screenDc, reftopPos, refsize,

memDc, refpointSource, 0, refblend, Win32.ULW_ALPHA);

}

finally{

Win32.ReleaseDC(IntPtr.Zero, screenDc);

if(hBitmap != IntPtr.Zero)

{

Win32.SelectObject(memDc, oldBitmap);

Win32.DeleteObject(hBitmap);

}

Win32.DeleteDC(memDc);

}

}

OK,到此为止,我们需要的圆角、渐变的阴影,都有了!

第三部分:简单的换肤机制

大家可能已经注意到,上面的代码有一个名为 SkinOptions 的东西。为了实现皮肤参数的统一传递和包装,我实现了 SkinOptions 这个类,用于保存当前皮肤的所有参数信息。

///

///皮肤风格参数/// public classSkinOptions: ICloneable{

///

///四边阴影的宽度。默认为 6。/// public intShadowWidth = 6;

///

///主背景圆角半径。最小值为 1,默认为 4。/// public intCornerRadius = 4;

///

///界面整体透明度。值范围 0~255。/// public byteOpacity = 255;

///

///边框宽度。默认为 1。/// public intBorderWidth = 1;

///

///边框颜色/// publicColorBorderColor = Color.FromArgb(255, 128, 128, 128);

///

///四边阴影的颜色。[0]为阴影内沿颜色,[1]为阴影外沿颜色/// publicColor[] ShadowColor = { Color.FromArgb(60, 0, 0, 0), Color.FromArgb(0, 0, 0, 0) };

///

///圆角阴影的颜色。[0]为阴影内沿颜色,[1]为阴影外沿颜色。///注:一般来讲,圆角阴影内沿的颜色应当比四边阴影内沿的颜色更深,才会有更好的显示效果。此值应当根据您的实际情况而定。///

/// 由于给扇面上渐变时,起点并不是准确的扇面内弧,因此扇面的内沿颜色可能应比四边的内沿颜色深publicColor[] CornerColor = { Color.FromArgb(180, 0, 0, 0), Color.FromArgb(0, 0, 0, 0) };

///

///高光颜色。[0]为高光边框左上角点的颜色,[1]为高光边框右下角的颜色/// publicColor[] BorderHighlightColor = { Color.FromArgb(200, 255, 255, 255), Color.FromArgb(200, 255, 255, 255) };

///

///高光过渡照射的角度。默认为“左下角到右上角对角线”的法线方向。/// public floatBorderHighlightAngle = 45f;

///

///背景高光。[0]为上下两端的颜色,[1]为中间高光的颜色/// publicColor[] BackgroundHighlightColor = { Color.FromArgb(0, 255, 255, 255), Color.FromArgb(200, 255, 255, 255) };

///

///窗体背景图/// publicImageBackgroundImage

{

get{

if(bgImg == null)

{

BitmapdefaultBmp = newBitmap(200, 100);

using(Graphicsg = Graphics.FromImage(defaultBmp))

{

g.FillRectangle(SystemBrushes.Control, 0, 0, 200, 100);

g.DrawString("未设置背景图", newFont(newFontFamily("宋体"), 9), SystemBrushes.GrayText, 50, 45);

bgImg = defaultBmp;

}

this.BackgroundLayout = ImageLayout.Tile;

}

returnbgImg;

}

set{

//if (null == value) throw new Exception("窗体背景图不能为 null 值。");bgImg = value;

}

}

privateImagebgImg;

///

///窗体背景图的显示方式/// publicImageLayoutBackgroundLayout = ImageLayout.None;

public staticSkinOptionsNewOne

{

get{

return newSkinOptions();

}

}

#regionICloneable 成员

public objectClone()

{

SkinOptionsresult = SkinOptions.NewOne;

result.BackgroundImage = (Image)this.BackgroundImage.Clone();

result.BackgroundLayout = this.BackgroundLayout;

result.BorderColor = this.BorderColor;

result.BorderWidth = this.BorderWidth;

result.CornerColor = (Color[])this.CornerColor.Clone();

result.CornerRadius = this.CornerRadius;

result.BorderHighlightAngle = this.BorderHighlightAngle;

result.BorderHighlightColor = (Color[])this.BorderHighlightColor.Clone();

result.BackgroundHighlightColor = (Color[])this.BackgroundHighlightColor.Clone();

result.Opacity = this.Opacity;

result.ShadowColor = (Color[])this.ShadowColor.Clone();

result.ShadowWidth = this.ShadowWidth;

returnresult;

}

#endregion}

有了这个类,我就可以很简单地通过切换这个类的实例,从而达到换肤的目的:

///

///应用新的皮肤风格(淡入淡出)///

/// 新皮肤风格。为 null 则取消皮肤public voidApplySkin(SkinOptionsnewSkin)

{

if(_isSkinChanging) return;

this._isSkinChanging = true;

CursoroldCursor = this.Cursor;

this.Cursor = Cursors.WaitCursor;

SkinEventArgse = newSkinEventArgs() {

Old = this.SkinOptions == null? null: (SkinOptions)this.SkinOptions.Clone(),

New = newSkin == null? null: (SkinOptions)newSkin.Clone()

};

// 触发皮肤切换之前的事件if(OnSkinPreChange != null) { OnSkinPreChange(this, e); }

if(newSkin == null) { newSkin = SkinOptions.NewOne; }

this.SkinOptions = newSkin;

SynchronizationContextcontext = SynchronizationContext.Current;

SendOrPostCallbackcheck = (a) =>

{

_isSkinChanging = false;

this.Cursor = oldCursor;

// 触发皮肤切换之后的事件if(OnSkinChanged != null) { OnSkinChanged(this, e); }

};

// 淡入操作SendOrPostCallbackshow = (a) =>

{

bytetmp = 0;

bytecurrentOpacity = newSkin.Opacity;

bufferdBackgroundImage = CreateBackground();

while(tmp < currentOpacity)

{

SetBitmap(bufferdBackgroundImage, tmp);

tmp += 5;

Thread.Sleep(5);

}

newThread((sc) => { ((SynchronizationContext)sc).Post(check, null); }) { Name = "检查切换操作是否完毕"}.Start(context);

};

// 淡出操作SendOrPostCallbackhide = (a) =>

{

// 渐变因子bytestep = 10;

SkinOptionsoldSkin = (SkinOptions)this.SkinOptions.Clone();

Bitmapoldbg = (Bitmap)bufferdBackgroundImage.Clone();

intcurrentOpacity = oldSkin.Opacity;

if(oldbg != null)

{

while(currentOpacity > step)

{

if(currentOpacity - (int)step <= 0) currentOpacity = step;

SetBitmap(oldbg, (byte)currentOpacity);

currentOpacity -= step;

Thread.Sleep(5);

}

}

newThread((sc) => { ((SynchronizationContext)sc).Post(show, null); }) { Name = "淡入新皮肤"}.Start(context);

};

newThread((sc) => { ((SynchronizationContext)sc).Post(hide, null); }) { Name = "淡出旧皮肤"}.Start(context);

}

private bool_isSkinChanging = false;

OK,到此为止,简单的换肤机制也完成了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值