Clipped View的裁剪和对齐方式

本文详细解析了ClipView的滚动实现原理,包括如何通过移动Panel和调整Clip范围来模拟内部条目的滚动效果。介绍了条目范围的概念,并给出了计算条目自动居上显示的具体公式。

ClipView的滚动原理是移动他自己这个Panel位置,然后同时移动他自己的Clip范围(由对应的shader完成),制造内部条目滚动的效果

 

一个条目范围是其内所有子元素的总范围(Bound Box),如果子元素大小超过了Clip范围,则子元素在Clip内左上对齐(自动拖拽的情况下)

ClipView的坐标计算不受UIGrid子元素个数的影响,与子元素的间距无关,与列表中排列在第一个位置的子元素的长宽和ClipView的Softness长宽有关

 

1、如需Clip中条目自动居上(不是居中),则需按照以下方式计算,以某个Clip的垂直滚动为例:
首先计算出ClipView这个Panel的Y方向上的偏移
公式为 ClipView.Y = ClipView.H / 2 - Softness.Y - Item.H / 2
(如 ClipView.H = 300 Item高度100 Softness.Y = 10 则 ClipView.Y = 90    300/2 - 10 - 100/2 = 90)
(如 ClipView.H = 400 Item高度80 Softness.Y = 6 则 ClipView.Y = 154    400/2 - 6 - 80/2 = 154)

将这个值填入 Transform.Y 和 -Clipping.Y (一正一负,重复一遍,其滚动原理是移动ClipView这个Panel,同时移动这个Clip的位置,使其看起来是在原地裁剪,实际上是在移动这个Panel)

 

2、ClipView 默认是居中对齐,按照NGUI的计算方式,如果需要移动整个Panel到某个位置,则按如下方式得出左上对齐的panel的坐标:
ClipView.X = ClipView.X + ClipView.W / 2
ClipView.Y = ClipView.Y - ClipView.H / 2

 

3、如果还有偏移,则在此基础上加上偏移量

 

4、另外一个方式是不修改ClipView的坐标(保持ClipView的坐标为0,0),修改UIGrid的坐标为(第一步)中计算的值,是一样的效果

 

因此,对单个条目的大小和Clip的大小,都应要求其尽量规范

internal class AnimationManager( private val targetView: View, private val config: SkeletonConfig ) { private var animator: ValueAnimator? = null private val appliedDrawables = mutableListOf<Pair<View, Drawable>>() private val paint = Paint().apply { isAntiAlias = true } /** * 启动 Shimmer 动画 * 若已有动画运行,则先停止再启动新动画 */ fun start() { stop() // 清理旧状态 animator = createShimmerAnimator() animator?.start() } /** * 停止当前动画并清理所有已添加的 overlay drawable */ fun stop() { animator?.cancel() animator = null appliedDrawables.forEach { (view, drawable) -> try { view.overlay.remove(drawable) } catch (e: Exception) { Log.e("AnimationManager", "Failed to remove overlay from view") } } appliedDrawables.clear() } /** * 创建控制 shimmer 动画的 ValueAnimator * * 动画范围从 -1f 到 2f,用于驱动 LinearGradient 水平移动,形成“扫光”效果 */ private fun createShimmerAnimator(): ValueAnimator { val animator = ValueAnimator.ofFloat(-1f, 2f).apply { duration = config.shimmerDuration repeatCount = ValueAnimator.INFINITE interpolator = AccelerateDecelerateInterpolator() addUpdateListener { animation -> val fraction = animation.animatedValue as Float updateShader(fraction) invalidateAllDrawables() } } applyShimmerToTargetView() return animator } /** * 更新渐变着色器(Shader),实现移动的高亮光效 */ private fun updateShader(offsetFraction: Float) { val width = targetView.width.toFloat() val height = targetView.height.toFloat() if (width <= 0f || height <= 0f) return // === 1. 计算光带实际宽度 === val bandWidth = width * config.shimmerWidth // === 2. 将角度转为弧度,并计算单位方向向量 === val angleRad = Math.toRadians(config.shimmerAngle.toDouble()) val cos = Math.cos(angleRad).toFloat() val sin = Math.sin(angleRad).toFloat() // === 3. 起始点偏移:让光带动起来 === // 总路径长度 ≈ 对角线长度,确保覆盖全区域 val diagonal = Math.sqrt((width * width + height * height).toDouble()).toFloat() val offset = offsetFraction * diagonal // 起点沿方向移动 val startX = -diagonal * cos + offset val startY = -diagonal * sin + offset val endX = startX + bandWidth * cos val endY = startY + bandWidth * sin // === 4. 创建 LinearGradient 并应用旋转 === val colors = intArrayOf(Color.TRANSPARENT, config.shimmerColor, Color.TRANSPARENT) val positions = floatArrayOf(0f, 0.5f, 1f) val shader = LinearGradient( startX, startY, endX, endY, colors, positions, Shader.TileMode.CLAMP ) // 👇 可选:如果还需要额外变换(比如缩放),可以用 Matrix paint.shader = shader } /** * 触发所有 shimmer drawable 重绘,使其显示最新 shader 效果 */ private fun invalidateAllDrawables() { appliedDrawables.forEach { (_, drawable) -> drawable.invalidateSelf() } } /** * 遍历目标视图及其子视图,为每个非 ViewGroup 的叶子视图创建并添加 shimmer drawable */ private fun applyShimmerToTargetView() { recursivelyApplyShimmer(targetView) } private fun recursivelyApplyShimmer(view: View) { if (view is ViewGroup) { for (i in 0 until view.childCount) { recursivelyApplyShimmer(view.getChildAt(i)) } } else { // 只有当视图具有有效尺寸时才应用 shimmer if (view.width <= 0 || view.height <= 0) return if (view.tag == "ignore_ani") return val shimmerDrawable = createShimmerDrawable(view) view.overlay.add(shimmerDrawable) appliedDrawables.add(view to shimmerDrawable) } } /** * 为指定视图创建一个自定义 Drawable,用于显示带有圆角支持的 shimmer 效果 */ private fun createShimmerDrawable(view: View): Drawable { val cornerRadius = config.cornerRadius.coerceAtLeast(0f) return object : Drawable() { override fun draw(canvas: Canvas) { val bounds = this.bounds if (bounds.isEmpty || paint.shader == null) return if (cornerRadius > 0f) { val rectF = RectF(bounds) val path = Path().apply { addRoundRect(rectF, cornerRadius, cornerRadius, Path.Direction.CW) } canvas.save() canvas.clipPath(path) canvas.drawRoundRect(rectF, cornerRadius, cornerRadius, paint) canvas.restore() } else { canvas.drawRect(bounds, paint) } } /** * 此处不处理 Alpha 设置,因为 shimmer 效果由 shader 控制透明度 */ override fun setAlpha(alpha: Int) { // 忽略:颜色透明度由 LinearGradient 决定 } /** * 不支持颜色过滤器 */ override fun setColorFilter(colorFilter: ColorFilter?) { // 忽略:shimmer 是纯图形动画,不需要滤镜 } /** * 返回半透明像素格式,表示此 Drawable 支持透明度 */ override fun getOpacity(): Int = PixelFormat.TRANSLUCENT }.apply { setBounds(0, 0, view.width, view.height) } } } 这个代码还是有问题,主要是光带在当前view上扫过的时候,由于光带是有颜色的,会导致圆角的地方也被展示出来,该怎么解决,按理来讲clipPath不是就可以做到这个了吗
12-08
import numpy as np import pandas as pd import torch import torch.nn as nn import torch.optim as optim from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.impute import SimpleImputer from sklearn.model_selection import KFold from sklearn.metrics import mean_squared_log_error # 1. 数据读取与初步分析 train_df = pd.read_csv('./data/house-prices-advanced-regression-techniques/train.csv') test_df = pd.read_csv('./data/house-prices-advanced-regression-techniques/test.csv') # 保存测试集ID test_ids = test_df['Id'] # 2. 数据预处理函数 def preprocess_data(df): # 处理缺失值 - 分类特征 cat_na_fill = { 'Alley': 'NoAlley', 'BsmtQual': 'NoBsmt', 'BsmtCond': 'NoBsmt', 'BsmtExposure': 'NoBsmt', 'BsmtFinType1': 'NoBsmt', 'BsmtFinType2': 'NoBsmt', 'FireplaceQu': 'NoFireplace', 'GarageType': 'NoGarage', 'GarageFinish': 'NoGarage', 'GarageQual': 'NoGarage', 'GarageCond': 'NoGarage', 'PoolQC': 'NoPool', 'Fence': 'NoFence', 'MiscFeature': 'None', 'MasVnrType': 'None' } df = df.fillna(cat_na_fill) # 处理缺失值 - 数值特征 (用均值填充) num_cols = df.select_dtypes(include=np.number).columns num_imputer = SimpleImputer(strategy='mean') df[num_cols] = num_imputer.fit_transform(df[num_cols]) return df # 3. 应用预处理 train_df = preprocess_data(train_df) test_df = preprocess_data(test_df) # 4. 特征工程 target = 'SalePrice' X_train = train_df.drop(columns=['Id', target]) y_train = train_df[target] # 分离数值分类特征 num_cols = X_train.select_dtypes(include=np.number).columns.tolist() cat_cols = X_train.select_dtypes(exclude=np.number).columns.tolist() # One-Hot编码分类特征 - 使用sparse_output替代已弃用的sparse ohe = OneHotEncoder(sparse_output=False, handle_unknown='ignore') X_cat = ohe.fit_transform(X_train[cat_cols]) # 创建特征数据框 X_processed = pd.DataFrame( np.hstack([X_train[num_cols].values, X_cat]), columns=num_cols + ohe.get_feature_names_out(cat_cols).tolist() ) # 5. 数据标准化 scaler = StandardScaler() X_scaled = scaler.fit_transform(X_processed) y_log = np.log1p(y_train) # 对数转换目标变量 # 6. PyTorch模型定义 class HousePriceModel(nn.Module): def __init__(self, input_size): super().__init__() self.linear = nn.Linear(input_size, 1) def forward(self, x): return self.linear(x) # 7. 训练配置 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') k_folds = 5 kf = KFold(n_splits=k_folds, shuffle=True, random_state=42) results = [] final_predictions = np.zeros(len(test_df)) # 8. K折交叉验证 for fold, (train_idx, val_idx) in enumerate(kf.split(X_scaled)): print(f"\n=== Fold {fold+1}/{k_folds} ===") # 数据拆分 X_train_fold, X_val_fold = X_scaled[train_idx], X_scaled[val_idx] y_train_fold, y_val_fold = y_log.iloc[train_idx], y_log.iloc[val_idx] # 转换为PyTorch张量 train_data = torch.tensor(X_train_fold, dtype=torch.float32).to(device) train_labels = torch.tensor(y_train_fold.values, dtype=torch.float32).view(-1, 1).to(device) val_data = torch.tensor(X_val_fold, dtype=torch.float32).to(device) val_labels = torch.tensor(y_val_fold.values, dtype=torch.float32).view(-1, 1).to(device) # 初始化模型 model = HousePriceModel(input_size=X_scaled.shape[1]).to(device) criterion = nn.MSELoss() optimizer = optim.Adam(model.parameters(), lr=0.001) # 训练循环 epochs = 500 for epoch in range(epochs): optimizer.zero_grad() outputs = model(train_data) loss = criterion(outputs, train_labels) loss.backward() optimizer.step() if (epoch+1) % 100 == 0: print(f"Epoch {epoch+1}/{epochs}, Loss: {loss.item():.4f}") # 验证预测 with torch.no_grad(): model.eval() val_preds = model(val_data).cpu().numpy().flatten() # 计算RMSLE (对数均方根误差) rmsle = np.sqrt(mean_squared_log_error( np.expm1(y_val_fold), np.expm1(val_preds) )) results.append(rmsle) print(f"Validation RMSLE: {rmsle:.5f}") # 测试集预测 X_test = test_df.drop(columns=['Id']) X_test_cat = ohe.transform(X_test[cat_cols]) X_test_final = np.hstack([X_test[num_cols].values, X_test_cat]) X_test_scaled = scaler.transform(X_test_final) test_tensor = torch.tensor(X_test_scaled, dtype=torch.float32).to(device) with torch.no_grad(): fold_preds = model(test_tensor).cpu().numpy().flatten() final_predictions += np.expm1(fold_preds) / k_folds # 9. 结果分析 print("\n=== Cross-validation Results ===") print(f"Mean RMSLE: {np.mean(results):.5f}") print(f"Std RMSLE: {np.std(results):.5f}") # 10. 生成提交文件 submission = pd.DataFrame({ 'Id': test_ids, 'SalePrice': final_predictions }) submission.to_csv('submission3.csv', index=False) print("Submission file saved successfully!") ValueError Traceback (most recent call last) Cell In[66], line 118 115 val_preds = model(val_data).cpu().numpy().flatten() 117 # 计算RMSLE (对数均方根误差) --> 118 rmsle = np.sqrt(mean_squared_log_error( 119 np.expm1(y_val_fold), 120 np.expm1(val_preds) 121 )) 122 results.append(rmsle) 123 print(f"Validation RMSLE: {rmsle:.5f}") File ~\.conda\envs\d2l\lib\site-packages\sklearn\utils\_param_validation.py:216, in validate_params.<locals>.decorator.<locals>.wrapper(*args, **kwargs) 210 try: 211 with config_context( 212 skip_parameter_validation=( 213 prefer_skip_nested_validation or global_skip_validation 214 ) 215 ): --> 216 return func(*args, **kwargs) 217 except InvalidParameterError as e: 218 # When the function is just a wrapper around an estimator, we allow 219 # the function to delegate validation to the estimator, but we replace 220 # the name of the estimator by the name of the function in the error 221 # message to avoid confusion. 222 msg = re.sub( 223 r"parameter of \w+ must be", 224 f"parameter of {func.__qualname__} must be", 225 str(e), 226 ) File ~\.conda\envs\d2l\lib\site-packages\sklearn\metrics\_regression.py:746, in mean_squared_log_error(y_true, y_pred, sample_weight, multioutput) 741 _, y_true, y_pred, _, _ = _check_reg_targets_with_floating_dtype( 742 y_true, y_pred, sample_weight, multioutput, xp=xp 743 ) 745 if xp.any(y_true <= -1) or xp.any(y_pred <= -1): --> 746 raise ValueError( 747 "Mean Squared Logarithmic Error cannot be used when " 748 "targets contain values less than or equal to -1." 749 ) 751 return mean_squared_error( 752 xp.log1p(y_true), 753 xp.log1p(y_pred), 754 sample_weight=sample_weight, 755 multioutput=multioutput, 756 ) ValueError: Mean Squared Logarithmic Error cannot be used when targets contain values less than or equal to -1.
10-27
源码地址: https://pan.quark.cn/s/d1f41682e390 miyoubiAuto 米游社每日米游币自动化Python脚本(务必使用Python3) 8更新:更换cookie的获取地址 注意:禁止在B站、贴吧、或各大论坛大肆传播! 作者已退游,项目不维护了。 如果有能力的可以pr修复。 小引一波 推荐关注几个非常可爱有趣的女孩! 欢迎B站搜索: @嘉然今天吃什么 @向晚大魔王 @乃琳Queen @贝拉kira 第三方库 食用方法 下载源码 在Global.py中设置米游社Cookie 运行myb.py 本地第一次运行时会自动生产一个文件储存cookie,请勿删除 当前仅支持单个账号! 获取Cookie方法 浏览器无痕模式打开 http://user.mihoyo.com/ ,登录账号 按,打开,找到并点击 按刷新页面,按下图复制 Cookie: How to get mys cookie 当触发时,可尝试按关闭,然后再次刷新页面,最后复制 Cookie。 也可以使用另一种方法: 复制代码 浏览器无痕模式打开 http://user.mihoyo.com/ ,登录账号 按,打开,找到并点击 控制台粘贴代码并运行,获得类似的输出信息 部分即为所需复制的 Cookie,点击确定复制 部署方法--腾讯云函数版(推荐! ) 下载项目源码压缩包 进入项目文件夹打开命令行执行以下命令 xxxxxxx为通过上面方式或取得米游社cookie 一定要用双引号包裹!! 例如: png 复制返回内容(包括括号) 例如: QQ截图20210505031552.png 登录腾讯云函数官网 选择函数服务-新建-自定义创建 函数名称随意-地区随意-运行环境Python3....
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Chadwi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值