Panel-Skin

本文详细介绍了如何通过自定义Adobe Panel的皮肤来实现个性化设计,包括编写整体框架皮肤、为标识符设置‘占位’以及为Panel中的属性设置样式。通过遵循特定步骤,用户可以创造出独特的用户界面元素,提升用户体验。

环境背景:

1、Adobe提供的模式皮肤,样式一般化不能给予好的体验:


 

自定义皮肤须知:

1、因为我们是对组件的皮肤“重写”,标签的标识符(Id)必须跟默认的一样。

 static private const exclusions:Array = ["background", "titleDisplay", "contentGroup", "controlBarGroup", "border"];


 

皮肤代码重写:

1、在设计模式下,右击Panel,选择“创建外观”,去掉“创建以下项的副本”,点击完成。

<?xml version="1.0" encoding="utf-8"?>
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009" 
		xmlns:s="library://ns.adobe.com/flex/spark" 
		xmlns:mx="library://ns.adobe.com/flex/mx">
	<!-- host component -->
	<fx:Metadata>
		[HostComponent("spark.components.Panel")]
	</fx:Metadata>
	
	<!-- states -->
	<s:states>
		<s:State name="disabledWithControlBar" />
		<s:State name="normalWithControlBar" />
		<s:State name="disabled" />
		<s:State name="normal" />
		<s:State name="disabled" />
		<s:State name="normal" />
	</s:states>
	
	<!-- SkinParts
	name=contentGroup, type=spark.components.Group, required=false
	name=titleDisplay, type=spark.core.IDisplayText, required=false
	name=controlBarGroup, type=spark.components.Group, required=false
	-->
</s:Skin>


2、编写Panel整体框架皮肤:

	<s:BorderContainer color="0xAAAAAA" 
					   cornerRadius="8"
					   width="100%" height="100%" 
					   borderColor="#aaaaaa" borderWeight="2">
		<s:Rect width="100%" height="45" 
				topLeftRadiusX="7" topLeftRadiusY="7" topRightRadiusX="7" topRightRadiusY="7"
				top="0">
			<s:fill>
				<s:LinearGradient rotation="90">
					<s:entries>
						<s:GradientEntry color="#bf0000" />
						<s:GradientEntry color="#ff0011" />
					</s:entries>
				</s:LinearGradient>
			</s:fill>
		</s:Rect>
		
		<s:Rect bottomLeftRadiusX="7" bottomRightRadiusX="7"
				y="45"
				width="100%" height="100%">
			<s:fill>
				<s:LinearGradient rotation="90">
					<s:entries>
						<s:GradientEntry color="#f8f8f8" />
						<s:GradientEntry color="#CCCCCC" />
					</s:entries>
				</s:LinearGradient>
			</s:fill>
		</s:Rect>
		
		<s:Rect width="100%" height="40" 
				excludeFrom="normal, disabled"
				bottomLeftRadiusX="7" bottomLeftRadiusY="7" bottomRightRadiusX="7" bottomRightRadiusY="7"
				bottom="0">
			<s:fill>
				<s:LinearGradient rotation="90">
					<s:entries>
						<s:GradientEntry color="#3f3f3f" />
						<s:GradientEntry color="#a5a5a5" />
					</s:entries>
				</s:LinearGradient>
			</s:fill>
		</s:Rect>
	</s:BorderContainer>

3、为已有的标识符设置“占位”:

	<s:Label id="titleDisplay" 
			 paddingLeft="15" paddingRight="15" paddingTop="15"
			 color="white"
			 fontSize="18"
			 fontWeight="bold"
			 fontStyle="italic"/>
	
	<s:Group id="contentGroup">
		<s:layout>
			<s:VerticalLayout paddingTop="55" paddingBottom="10"
							  paddingLeft="15" paddingRight="15"
							  paddingBottom.disabledWithControlBar="55" 
							  paddingTop.disabledWithControlBar="55"
							  paddingBottom.normalWithControlBar="55"
							  paddingTop.normalWithControlBar="55"/>
		</s:layout>
	</s:Group>
	
	<s:Group id="controlBarGroup" bottom="10">
		<s:layout>
			<s:HorizontalLayout paddingLeft="5" paddingRight="5"/>
		</s:layout>
	</s:Group>

4、运行结果:

5、在为panel中的属性设置值:

	<s:Panel id="myPanel1" skinClass="skins.PanelSkin" title="My first panel title">
		
		<mx:Text width="200" text="Test。 "/>
		
		<s:controlBarContent>
			<s:Button label="Button"/>
		</s:controlBarContent>
		
	</s:Panel>
	<s:Panel id="myPanel2" skinClass="skins.PanelSkin" title="My second panel title">
		
		<mx:Text width="200" text="Test。"/>
		
	</s:Panel>


 完整代码:

1、PanelSkin

<?xml version="1.0" encoding="utf-8"?>
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009" 
		xmlns:s="library://ns.adobe.com/flex/spark" 
		xmlns:mx="library://ns.adobe.com/flex/mx">
	<!-- host component -->
	<fx:Metadata>
		[HostComponent("spark.components.Panel")]
	</fx:Metadata>
	
	<!-- states -->
	<s:states>
		<s:State name="disabledWithControlBar" />
		<s:State name="normalWithControlBar" />
		<s:State name="disabled" />
		<s:State name="normal" />
	</s:states>
	
	<!-- SkinParts
	name=contentGroup, type=spark.components.Group, required=false
	name=titleDisplay, type=spark.core.IDisplayText, required=false
	name=controlBarGroup, type=spark.components.Group, required=false
	-->
	<s:BorderContainer color="0xAAAAAA" 
					   cornerRadius="8"
					   width="100%" height="100%" 
					   borderColor="#aaaaaa" borderWeight="2">
		<s:Rect width="100%" height="45" 
				topLeftRadiusX="7" topLeftRadiusY="7" topRightRadiusX="7" topRightRadiusY="7"
				top="0">
			<s:fill>
				<s:LinearGradient rotation="90">
					<s:entries>
						<s:GradientEntry color="#bf0000" />
						<s:GradientEntry color="#ff0011" />
					</s:entries>
				</s:LinearGradient>
			</s:fill>
		</s:Rect>
		
		<s:Rect bottomLeftRadiusX="7" bottomRightRadiusX="7"
				y="45"
				width="100%" height="100%">
			<s:fill>
				<s:LinearGradient rotation="90">
					<s:entries>
						<s:GradientEntry color="#f8f8f8" />
						<s:GradientEntry color="#CCCCCC" />
					</s:entries>
				</s:LinearGradient>
			</s:fill>
		</s:Rect>
		
		<s:Rect width="100%" height="40" 
				excludeFrom="normal, disabled"
				bottomLeftRadiusX="7" bottomLeftRadiusY="7" 
				bottomRightRadiusX="7" bottomRightRadiusY="7"
				bottom="0">
			<s:fill>
				<s:LinearGradient rotation="90">
					<s:entries>
						<s:GradientEntry color="#3f3f3f" />
						<s:GradientEntry color="#a5a5a5" />
					</s:entries>
				</s:LinearGradient>
			</s:fill>
		</s:Rect>
	</s:BorderContainer>
	
	<s:Label id="titleDisplay" 
			 paddingLeft="15" paddingRight="15" paddingTop="15"
			 color="white"
			 fontSize="18"
			 fontWeight="bold"
			 fontStyle="italic"/>
	
	<s:Group id="contentGroup">
		<s:layout>
			<s:VerticalLayout paddingTop="55" paddingBottom="10"
							  paddingLeft="15" paddingRight="15"
							  paddingBottom.disabledWithControlBar="55" 
							  paddingTop.disabledWithControlBar="55"
							  paddingBottom.normalWithControlBar="55"
							  paddingTop.normalWithControlBar="55"/>
		</s:layout>
	</s:Group>
	
	<s:Group id="controlBarGroup" bottom="10">
		<s:layout>
			<s:HorizontalLayout paddingLeft="5" paddingRight="5"/>
		</s:layout>
	</s:Group>
</s:Skin>

 

2、Application

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
			   xmlns:s="library://ns.adobe.com/flex/spark"
			   xmlns:mx="library://ns.adobe.com/flex/mx"
			   minWidth="955" minHeight="600">
	<s:layout>
		<s:HorizontalLayout horizontalAlign="center" paddingTop="5" verticalAlign="middle"/>
	</s:layout>
	<fx:Declarations>
		<!-- 将非可视元素(例如服务、值对象)放在此处 -->
	</fx:Declarations>
	<s:Panel width="250" height="200" skinClass="skins.PanelSkin">
	</s:Panel>
	
	<s:Panel id="myPanel1" skinClass="skins.PanelSkin" title="My first panel title">
		
		<mx:Text width="200" text="Test。 "/>
		
		<s:controlBarContent>
			<s:Button label="Button"/>
		</s:controlBarContent>
		
	</s:Panel>
	
	<s:Panel id="myPanel2" skinClass="skins.PanelSkin" title="My second panel title">
		
		<mx:Text width="200" text="Test。"/>
		
	</s:Panel>

</s:Application>

 

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Video.js 右侧播放列表</title> <link href="https://vjs.zencdn.net/7.15.4/video-js.css" rel="stylesheet"> <style> .player-container { display: flex; max-width: 1200px; margin: 20px auto; gap: 10px; } .video-js { flex: 1; height: 400px; } .playlist-container { width: 300px; background: #2B333F; overflow-y: auto; padding: 5px; } .playlist-header { color: white; padding: 10px; font-size: 18px; border-bottom: 1px solid #3E4A5A; } .playlist-item { display: flex; padding: 8px; margin-bottom: 5px; background: #38414F; border-radius: 4px; cursor: pointer; transition: background 0.3s; } .playlist-item:hover { background: #475569; } .playlist-item.active { background: #4E5D6C; border-left: 3px solid #1AB7EA; } .playlist-thumbnail { width: 80px; height: 45px; margin-right: 10px; background-size: cover; background-position: center; } .playlist-title { color: #E0E0E0; font-size: 14px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } /* 自定义下一集按钮样式 */ .vjs-next-episode { cursor: pointer; flex: none; width: 5em !important; } .vjs-next-episode .vjs-icon-placeholder:before { content: "▶|"; font-size: 10px; line-height: 2; } .vjs-next-episode:hover { color: #fff; } /* 快捷键提示效果 */ .vjs-keyboard-feedback { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: rgba(0, 0, 0, 0.7); color: white; padding: 15px 25px; border-radius: 5px; font-size: 24px; z-index: 100; display: none; pointer-events: none; } /* 非全屏时的快捷键提示位置 */ .vjs-has-started:not(.vjs-fullscreen) .vjs-keyboard-feedback { top: 60%; left: 50%; transform: translateX(-50%); } /* 时间跳转提示 */ .vjs-time-jump { font-size: 36px; } /* 音量变化提示 */ .vjs-volume-change { font-size: 36px; } /* 自定义垂直音量控制 */ .vjs-volume-panel.vjs-volume-panel-vertical { width: 4em; height: 8em; display: flex; flex-direction: column-reverse; } .vjs-volume-panel-vertical .vjs-volume-control { width: 100%; height: 5em; margin: 0; position: relative; } .vjs-volume-panel-vertical .vjs-volume-bar { width: 0.5em; height: 100%; margin: 0 auto; } .vjs-volume-panel-vertical .vjs-volume-level { width: 100%; height: 100%; } .vjs-volume-panel-vertical .vjs-volume-level:before { top: -0.5em; left: -0.25em; } /* 快捷键帮助面板 */ .vjs-shortcut-help { position: absolute; bottom: 60px; left: 50%; transform: translateX(-50%); background-color: rgba(0, 0, 0, 0.8); color: white; padding: 15px; border-radius: 5px; z-index: 100; display: none; width: 80%; max-width: 600px; pointer-events: none; } .vjs-shortcut-help h3 { margin-top: 0; margin-bottom: 10px; text-align: center; } .vjs-shortcut-help-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; } .vjs-shortcut-help-item { display: flex; align-items: center; } .vjs-shortcut-key { background: #333; padding: 5px 10px; border-radius: 3px; margin-right: 10px; font-family: monospace; } /* 在非全屏时显示帮助面板 */ .vjs-has-started:not(.vjs-fullscreen) .vjs-shortcut-help { display: block; } </style> </head> <body> <div class="player-container"> <video id="my-video" class="video-js vjs-default-skin" controls preload="auto"> <source src="video/01_云霄飞车杀人事件.mp4" type="video/mp4"> </video> <div class="playlist-container"> <div class="playlist-header">播放列表</div> <div id="playlist-items"></div> </div> </div> <script src="https://vjs.zencdn.net/7.15.4/video.min.js"></script> <script> document.addEventListener('DOMContentLoaded', function() { // 视频列表数据 const videos = [ { title: "视频标题1", src: "video/01_云霄飞车杀人事件.mp4", thumbnail: "https://example.com/thumbnail1.jpg", type: "video/mp4" }, { title: "视频标题2", src: "https://example.com/video2.mp4", thumbnail: "https://example.com/thumbnail2.jpg", type: "video/mp4" }, { title: "视频标题3", src: "https://example.com/video3.mp4", thumbnail: "https://example.com/thumbnail3.jpg", type: "video/mp4" }, // 可以添加更多视频... ]; // 初始化播放器 - 修改控制栏组件顺序 const player = videojs('my-video', { controls: true, autoplay: false, preload: 'auto', controlBar: { children: [ 'playToggle', // 播放/暂停按钮 'CustomNextEpisodeButton', // 自定义下一集按钮 'progressControl', // 进度条 'CustomVolumePanel', // 自定义垂直音量控制 'pictureInPictureToggle', // 画中画按钮 'fullscreenToggle' // 全屏按钮 ] } }); // 创建快捷键反馈元素 const keyboardFeedback = document.createElement('div'); keyboardFeedback.className = 'vjs-keyboard-feedback'; player.el().appendChild(keyboardFeedback); // 显示快捷键反馈 function showKeyboardFeedback(text, className = '') { keyboardFeedback.textContent = text; keyboardFeedback.className = `vjs-keyboard-feedback ${className}`; keyboardFeedback.style.display = 'block'; // 2秒后自动隐藏 clearTimeout(keyboardFeedback.timer); keyboardFeedback.timer = setTimeout(() => { keyboardFeedback.style.display = 'none'; }, 2000); } // 添加下一集按钮 const NextEpisodeButton = videojs.getComponent('Button'); class CustomNextEpisodeButton extends NextEpisodeButton { constructor() { super(player); } handleClick() { const currentIndex = getCurrentVideoIndex(); if (currentIndex < videos.length - 1) { playVideo(currentIndex + 1); } else { videojs.log('已经是最后一集'); } } buildCSSClass() { return 'vjs-next-episode vjs-control'; } } videojs.registerComponent('CustomNextEpisodeButton', CustomNextEpisodeButton); player.getChild('controlBar').addChild('CustomNextEpisodeButton', {}, 1); // 自定义垂直音量控制面板 const VolumePanel = videojs.getComponent('VolumePanel'); class CustomVolumePanel extends VolumePanel { constructor() { super(player); this.addClass('vjs-volume-panel-vertical'); this.removeClass('vjs-volume-panel-horizontal'); } handleClick(event) { // 防止点击事件冒泡,避免触发播放/暂停 event.stopPropagation(); super.handleClick(event); } } videojs.registerComponent('CustomVolumePanel', CustomVolumePanel); // 获取当前播放视频的索引 function getCurrentVideoIndex() { const currentSrc = player.currentSrc(); return videos.findIndex(video => video.src === currentSrc); } // 播放指定索引的视频 function playVideo(index) { const video = videos[index]; // 更新播放列表激活状态 document.querySelectorAll('.playlist-item').forEach((el, i) => { if (i === index) { el.classList.add('active'); } else { el.classList.remove('active'); } }); // 切换视频 player.src({ src: video.src, type: video.type }); player.poster(video.thumbnail); player.load(); player.play(); } // 渲染播放列表 const playlistContainer = document.getElementById('playlist-items'); function renderPlaylist() { playlistContainer.innerHTML = ''; videos.forEach((video, index) => { const item = document.createElement('div'); item.className = 'playlist-item'; if (index === 0) item.classList.add('active'); item.innerHTML = ` <div class="playlist-thumbnail" style="background-image: url('${video.thumbnail}')"></div> <div class="playlist-title">${video.title}</div> `; item.addEventListener('click', () => { playVideo(index); }); playlistContainer.appendChild(item); }); } // 初始加载第一个视频 if (videos.length > 0) { player.src({ src: videos[0].src, type: videos[0].type }); player.poster(videos[0].thumbnail); renderPlaylist(); } // 添加键盘快捷键功能 document.addEventListener('keydown', function(e) { // 只在播放器激活时响应快捷键 if (!player.hasClass('vjs-has-started') && e.target.tagName !== 'BODY') { return; } // 防止快捷键与浏览器默认行为冲突 if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') { return; } switch (e.key.toLowerCase()) { case ' ': // 空格键 - 播放/暂停 e.preventDefault(); if (player.paused()) { player.play(); showKeyboardFeedback('播放', 'vjs-play-pause'); } else { player.pause(); showKeyboardFeedback('暂停', 'vjs-play-pause'); } break; case 'arrowleft': // 左箭头 - 后退5秒 e.preventDefault(); player.currentTime(Math.max(0, player.currentTime() - 5)); showKeyboardFeedback('-5秒', 'vjs-time-jump'); break; case 'arrowright': // 右箭头 - 前进5秒 e.preventDefault(); player.currentTime(Math.min(player.duration(), player.currentTime() + 5)); showKeyboardFeedback('+5秒', 'vjs-time-jump'); break; case 'arrowup': // 上箭头 - 音量增加 e.preventDefault(); const newVolUp = Math.min(1, player.volume() + 0.1); player.volume(newVolUp); showKeyboardFeedback(`音量 ${Math.round(newVolUp * 100)}%`, 'vjs-volume-change'); break; case 'arrowdown': // 下箭头 - 音量减少 e.preventDefault(); const newVolDown = Math.max(0, player.volume() - 0.1); player.volume(newVolDown); showKeyboardFeedback(`音量 ${Math.round(newVolDown * 100)}%`, 'vjs-volume-change'); break; } }); }); </script> </body> </html>删除基于video.js控制栏上所有按钮
06-02
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值