65.9K
CodeProject 正在变化。 阅读更多。
Home

不仅仅是图片列表控件,简洁、3D、iTunes风格

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (19投票s)

2007 年 10 月 24 日

CPOL

4分钟阅读

viewsIcon

72219

downloadIcon

3538

该图片列表控件以3D方式显示所有图片,选中时会为项目添加动画效果,并允许控制项目的大小、透明度、位置和动画速度。

Screenshot - AlbumViewer.gif

引言

灵感来自于iTunes的专辑列表控件。这款图片列表控件可以让你以一种全新的3D方式展示图片。告别乏味的逐一显示图片,取而代之的是炫酷的动画效果。你可以控制未选中项目的缩放级别,以及阴影的高度、每个未选中项目的透明度、它们的位置和动画速度。

这是一款非常有用的控件,用于在GUI丰富的应用程序中显示图片。该控件派生自CListCtrl类,因此可以扩展以支持传统的列表控件视图或炫酷的3D动画视图。

背景

正如我之前提到的,灵感来自于iTunes的专辑控件。在周末享受音乐时,我一直在想,如何才能拥有一个像iTunes那样具有流畅、简洁动画的图片展示控件。于是我接受了挑战,发现只需要大约10个代数方程和对GDI+的巧妙运用就能实现。

该控件加载全尺寸图片,在演示中,它使用了高达800x600的图片。通过高效地使用GDI+,你可以流畅地进行动画、缩放和控制Alpha值,而不会出现任何闪烁。

本文档对于希望了解更多关于使用GDI+绘图的开发人员非常有帮助。尽管GDI+几乎包含了所需的一切,但无法像iTunes那样绘制图片。它允许对图片进行剪切,但不支持使图片的上下两边具有不同的高度。我并没有真正尝试使用矩阵变换,但我认为使用它们可以更好地实现图片的动画和定位。

Using the Code

要在你的项目中 S 用该控件,请添加AlubumCtrl.hAlbumCtrl.cpp,在你的窗体/对话框上添加一个列表控件,为此列表控件创建一个类型为CAlbumCtrl的成员变量,并在你的OnInitDialog或任何你想向列表控件添加新项目的其他地方添加以下代码。

    m_ctlAlbum.AddItem(L"images\\Water lilies.jpg");
    m_ctlAlbum.AddItem(L"images\\Sunset.jpg");
    m_ctlAlbum.AddItem(L"images\\Winter.jpg");
    m_ctlAlbum.AddItem(L"images\\Blue hills.jpg");

    m_ctlAlbum.SetCurrentItem(2);

    m_sldAlpha.SetRange(0,100);
    m_sldAnim.SetRange(0,100);
    m_sldShadow.SetRange(0,100);
    m_sldZoom.SetRange(0,100);
    m_sldElevation.SetRange(-300,100);

    m_sldElevation.SetPos(m_ctlAlbum.GetItemElevation());

    m_sldAlpha.SetPos(m_ctlAlbum.GetItemAlpha());
    m_sldAnim.SetPos(m_ctlAlbum.GetAnimSpeed());
    m_sldShadow.SetPos(m_ctlAlbum.GetItemShadow());
    m_sldZoom.SetPos(m_ctlAlbum.GetItemZoom());

计算项目位置

以下函数是放置控件最重要的函数,令人惊讶的是,代码量非常少。是的,在经过大约4轮的代数和代码微调后,才有了现在的样子。 :)

RectF CAlbumCtrl::CalcItemRect(const int n, const RectF rcBase, bool bLeft)
{
    float r = m_fRatio;
    float p = pow(r,n);
    int h = rcBase.Height;
    int w = rcBase.Width;
    int y = 0;
    int x = 0;

    for(int j=1;j<=n;j++)
        y += (m_nItemY*h*pow(r,j))/100;

    if( bLeft )
    {
        int x = 0;
        for(int j=2;j<=n+1;j++)
            x += w*pow(r,j);

        return RectF(rcBase.X-x, rcBase.Y-y, w*p, h*p);
    }
        
    x = rcBase.X + w;

    for(int j=1;j<n;j++)
        x += w*pow(r,j);

    for(int j=1;j<n+1;j++)
        x -= (1-r)*w*pow(r,j);

    return RectF(x, rcBase.Y-y, w*p, h*p);
}

绘制项目

这是控件中第二重要的函数。它获取项目的图片,计算阴影宽度,应用Alpha值到图片和逐渐减弱的阴影,如果需要,还会在选中的图片上放置文本。

void CAlbumCtrl::DrawItem
     (const int nItem, RectF rc, Graphics &grf, float fAlpha, bool bDrawText)
{
    // centre image
    // draw the items only if they are inside the visible area
    // to keep repainting fast

    if( nItem < 0 || nItem > m_vecItems.size()-1 )
        return;

    Bitmap bitmap(m_vecItems[nItem].wszItem.c_str());

    CRect r;
    GetClientRect(&r);

    RectF rcWnd(r.left, r.top, r.Width(), r.Height());
    if(!rcWnd.IntersectsWith(rc))
        return;

    Bitmap     bmp(rc.Width, rc.Height, PixelFormat32bppARGB );

    Graphics graphic(&bmp);

    ImageAttributes imgAttrb;

    RectF rcDraw(0, 0, rc.Width, rc.Height);
    graphic.DrawImage(&bitmap, rcDraw, 0, 0, bitmap.GetWidth(), 
                      bitmap.GetHeight(), UnitPixel, &imgAttrb);

    graphic.DrawRectangle(&Pen(Color(80,80,80), 2),rcDraw);
    
    ColorMatrix bmpAlpha = {
                             1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
                             0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
                             0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
                             0.0f, 0.0f, 0.0f, fAlpha, 0.0f,
                             0.0f, 0.0f, 0.0f, 0.0f, 1.0f
                            };

    imgAttrb.SetColorMatrix(&bmpAlpha);
    grf.DrawImage(&bmp, rc, 0, 0, rc.Width, rc.Height, UnitPixel, &imgAttrb);

    int iHeight = bmp.GetHeight();
    int iWidth = bmp.GetWidth();

    // reflection percent

    if( !m_nItemShadow )
        m_nItemShadow = 1;

    float nRef = 100.0/m_nItemShadow;
    RectF rcRef = RectF(rc.X, rc.Y+rc.Height, rc.Width, rc.Height/nRef);
    Bitmap     bmpRef(rcDraw.Width, rcDraw.Height/nRef, PixelFormat32bppARGB );

    Color color, colorTemp;
    for(UINT iRow = iHeight; iRow > iHeight-iHeight/nRef; iRow--)
    {
       for(UINT iColumn = 0; iColumn < iWidth; iColumn++)
       {
           // decrease the alpha by 1 value for each line in bottom margin
           double dAlpha = (iHeight/nRef-(iHeight-iRow))*255/(iHeight/nRef);
          bmp.GetPixel(iColumn, iRow, &color);
          colorTemp.SetValue(color.MakeARGB(
             //(BYTE)(5*(iHeight/nRef-(iHeight-iRow))), 
             (BYTE)dAlpha,
             color.GetRed(),
             color.GetGreen(),
             color.GetBlue()));
          bmpRef.SetPixel(iColumn, iHeight-iRow, colorTemp);
       }
    }
    grf.DrawImage(&bmpRef, rcRef, 0, 0, rcRef.Width, rcRef.Height, 
    UnitPixel, &imgAttrb);
    if( bDrawText )
    {
        //wstring wszText = m_vecItems[nItem].wszItem; 
        //Font myFont(L"Tahoma",12,FontStyleRegular,UnitPixel);
        //
        //RectF rcText;
        //grf.MeasureString(wszText.c_str(), wszText.length(), &myFont, 
    // rcRef, &rcText);
        //grf.DrawString(wszText.c_str(),wszText.length(), &myFont, 
    // PointF(rcRef.X+rcRef.Width-rcText.Width-14, 
    // rcRef.Y+(rcRef.Height-rcText.Height)/2-rcText.Height), 
    // &SolidBrush(Color(255,255,255))); 
        //wchar_t temp[10];
        //wszText = _itow(nItem+1, temp, 10);
        //grf.DrawString(wszText.c_str(),wszText.length(), &myFont, 
    // PointF(rcRef.X+2, rcRef.Y+(rcRef.Height-rcText.Height)/2-rcText.Height), 
    // &SolidBrush(Color(255,255,255))); 
        //grf.DrawString(wszText.c_str(),wszText.length(), &myFont, 
    // PointF(rcRef.X, rcRef.Y), &SolidBrush(Color(255,255,255))); 
    }
}

幕后涉及的数学原理

控件中最有趣的部分是用于定位和动画化项目的数学原理。其中,在中心选中项目左右两侧的项目定位/动画化方面,涉及不同的代数运算。

在控件中定位项目

计算任何项目的宽度

Wn = W(r/100)n

计算任何项目的高度

Hn = H(r/100)n

计算任何项目的Y坐标

Yn = Y - n

i = 1
H/e(r/100)

计算选中项目左侧项目的X坐标

Xn = X - n+1

i = 2
W(r/100)

计算选中项目右侧项目的X坐标

Xn = X + W + n-1

i = 1
W(r/100)i - n+1

i = 1
W(1-r/100)(r/100)

其中

  • H = 选中项目的高度
  • W = 选中项目的宽度
  • X = 选中项目的X坐标
  • Y = 选中项目的Y坐标
  • r = 当前缩放级别(%)
  • e = 当前Y轴的抬升值

在控件中动画化项目

向左动画化项目

左侧动画化项目

∆X = c(Xn+1-Xn)/l
Xn = Xn + ∆X

∆Y = c(Yn+1-Yn)/l

Yn = Yn + ∆Y


∆W = c(Wn+1-Wn)/l
Wn = Wn + ∆W

∆H = c(Hn+1-Hn)/l
Hn = Hn + ∆H

右侧动画化项目

∆X = c(Xn+1-Xn)/l
Xn-1 = Xn + ∆X

∆Y = c(Yn+1-Yn)/l
Yn-1 = Yn - ∆Y

∆W = c(Wn+1-Wn)/l
Wn = Wn - ∆W

∆H = c(Hn+1-Hn)/l
Hn = Hn - ∆H

向右动画化项目

左侧动画化项目

∆X = c(Xn+1-Xn)/l
Xn = Xn + ∆X

∆Y = c(Yn+1-Yn)/l

Yn = Yn + ∆Y


∆W = c(Wn+1-Wn)/l
Wn = Wn + ∆W

∆H = c(Hn+1-Hn)/l
Hn = Hn + ∆H

右侧动画化项目

∆X = c(Xn+1-Xn)/l
Xn = Xn + ∆X

∆Y = c(Yn+1-Yn)/l
Yn = Yn - ∆Y

∆W = c(Wn-1-Wn)/l
Wn = Wn - ∆W

∆H = c(Hn-1-Hn)/l
Hn = Hn - ∆H

其中

  • c = 当前动画步长
  • l = 总动画步数

未来功能

我计划添加视图切换按钮,以便用户可以在图标视图/列表视图/报表视图/专辑视图之间来回切换。

历史

  • 2007年10月29日:初始版本
© . All rights reserved.