UE4 GAS/ActionRPG学习导图——AttributeSet(Gameplay Ability System)

FGameplayAttributeData - 用于存储某个属性的值

两个基础属性为:BaseValue、CurrentValue

  • BaseValue:恒定的基础值
  • CurrentValue:BaseValue加上GameplayEffects的临时修改值后的值(方便效果失效后的回滚)

FGameplayAttribute - 作为一个名字的标识使用UAttributeSet以及本身

三者的联系可以看这一段代码,我个人认为FGameplayAttribute 类似于中间的服务商,通过UAttributeSet和FStructProperty获取对应的FGameplayAttributeData。
在这里插入图片描述

GetGameplayAttributeDataChecked只不过比GetGameplayAttributeData多了一个非法判断,即当Src && IsGameplayAttributeDataProperty(Attribute.Get())不合法时check(false);

PreAttributeChange()

当Attribute的CurrentValue被改变之前调用。例如增加移速后不能突破上限。

但是,不要修改Attribute,否则会死循环,直接修改NewValue(最后要变的值)即可。

void URoleAttributeSet::MaxValueChanged(FGameplayAttributeData& AffectedAttribute, const FGameplayAttributeData& MaxAttribute, float NewMaxValue, const FGameplayAttribute& AffectedAttributeProperty)
{
	UAbilitySystemComponent* ASC = GetOwningAbilitySystemComponent();
	const float CurrentMaxValue = MaxAttribute.GetCurrentValue();
	if (!FMath::IsNearlyEqual(CurrentMaxValue, NewMaxValue) && ASC)
	{
		const float CurrentValue = AffectedAttribute.GetCurrentValue();
		float NewDelta = (CurrentMaxValue > 0.f) ? (CurrentValue * NewMaxValue / CurrentMaxValue) - CurrentValue : NewMaxValue;
		ASC->ApplyModToAttributeUnsafe(AffectedAttributeProperty, EGameplayModOp::Additive, NewDelta);
	}
}
void URoleAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
	Super::PreAttributeChange(Attribute, NewValue);
	if (Attribute == GetMaxHealthAttribute())
	{
		MaxValueChanged(Health, MaxHealth, NewValue, GetHealthAttribute());
	}
	else if (Attribute == GetMaxManaAttribute())
	{
		MaxValueChanged(Mana, MaxMana, NewValue, GetManaAttribute());
	}
	else if (Attribute == GetHealthAttribute())
	{
		NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxHealth());
	}
	else if (Attribute == GetManaAttribute())
	{
		NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxMana());
	}
}

官方不推荐使用PreAttributeChange()处理游戏性事件

监听Attribute的改变进行游戏性事件处理(比如角色上的血条)最好使用UAbilitySystemComponent::GetGameplayAttributeValueChangeDelegate(FGameplayAttribute Attribute)

PostGameplayEffectExecute()

仅在instant GameplayEffect使Attribute的 BaseValue改变时触发,可以把涉及伤害相关的写这里。

GetGameplayAttributeValueChangeDelegate(FGameplayAttribute Attribute)

属性修改后的处理

PlayerState:

	FDelegateHandle HandleHealthChanged;
	virtual void HealthChanged(const FOnAttributeChangeData& Data);
void AProjectWPlayerState::BeginPlay() {
	if (AS && ASC) {
		HandleHealthChanged = ASC->GetGameplayAttributeValueChangeDelegate(
			AS->GetHealthAttribute()).AddUObject(this, &AProjectWPlayerState::HealthChanged);
	}
}
void AProjectWPlayerState::HealthChanged(const FOnAttributeChangeData& Data)
{
	if (GetPawn()->GetController()) {
		UProjectWHUD* Widget = Cast<APlayerControllerInGame>(GetPawn()->GetController())->Widget;
		if (Widget) {
			Widget->UpdateHealthBar(Data.NewValue);
		}
	}
}

获取ASC

GetOwningAbilitySystemComponent()

属性名称

FString AttributeName

Attribute和AttributeOwner

TFieldPath<FProperty> Attribute;
UStruct* AttributeOwner;

修改属性

MyAbilitySystemComponent->ApplyModToAttributeUnsafe(MyGameplayAttribute, EGameplayModOp::Additive, Delta);

修改属性触发

FDelegateHandle HealthChangedDelegateHandle;
HealthChangedDelegateHandle = AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(
	AttributeSetBase->GetHealthAttribute()).AddUObject(this, &PlayerState::HealthChanged);

在编辑器内隐藏

Meta = (HideInDetailsView)

有一个比较有意思的宏

只需要ATTRIBUTE_ACCESSORS(URPGAttributeSet, Health)即可GetHealthAttribute、GetHealth、SetHealth、InitHealth

// 加到.h内
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
	GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
#define GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
	static FGameplayAttribute Get##PropertyName##Attribute() \
	{ \
		static FProperty* Prop = FindFieldChecked<FProperty>(ClassName::StaticClass(), GET_MEMBER_NAME_CHECKED(ClassName, PropertyName)); \
		return Prop; \
	}

#define GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
	FORCEINLINE float Get##PropertyName() const \
	{ \
		return PropertyName.GetCurrentValue(); \
	}

#define GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
	FORCEINLINE void Set##PropertyName(float NewVal) \
	{ \
		UAbilitySystemComponent* AbilityComp = GetOwningAbilitySystemComponent(); \
		if (ensure(AbilityComp)) \
		{ \
			AbilityComp->SetNumericAttributeBase(Get##PropertyName##Attribute(), NewVal); \
		}; \
	}

#define GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName) \
	FORCEINLINE void Init##PropertyName(float NewVal) \
	{ \
		PropertyName.SetBaseValue(NewVal); \
		PropertyName.SetCurrentValue(NewVal); \
	}

UAttributeSet - 属性数据集合

本身并没有提供存储FGameplayAttribute 的容器,而是通过派生子类的形式追加属性

运行时追加/删除属性集

类似于获得武器的功能,可能会得到不同的属性集

AbilitySystemComponent->SpawnedAttributes.AddUnique(WeaponAttributeSetPointer);
AbilitySystemComponent->ForceReplication();
AbilitySystemComponent->SpawnedAttributes.AddUnique(WeaponAttributeSetPointer);
AbilitySystemComponent->ForceReplication();

使用表格初始化

添加一个GE,在Root里面UseEffect,进行初始化。注意,一定要先写MaxHealth,再写Health,否则会被自己的限制,将Health变为0。
在这里插入图片描述

官方ARPG代码示例

// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "ActionRPG.h"
#include "AttributeSet.h"
#include "AbilitySystemComponent.h"
#include "RPGAttributeSet.generated.h"

// Uses macros from AttributeSet.h
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
	GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)

/** This holds all of the attributes used by abilities, it instantiates a copy of this on every character */
UCLASS()
class ACTIONRPG_API URPGAttributeSet : public UAttributeSet
{
	GENERATED_BODY()
public:

	// Constructor and overrides
	URPGAttributeSet();
	virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
	virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override;
	virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

	/** Current Health, when 0 we expect owner to die. Capped by MaxHealth */
	UPROPERTY(BlueprintReadOnly, Category = "Health", ReplicatedUsing=OnRep_Health)
	FGameplayAttributeData Health;
	ATTRIBUTE_ACCESSORS(URPGAttributeSet, Health)

	/** MaxHealth is its own attribute, since GameplayEffects may modify it */
	UPROPERTY(BlueprintReadOnly, Category = "Health", ReplicatedUsing=OnRep_MaxHealth)
	FGameplayAttributeData MaxHealth;
	ATTRIBUTE_ACCESSORS(URPGAttributeSet, MaxHealth)

	/** Current Mana, used to execute special abilities. Capped by MaxMana */
	UPROPERTY(BlueprintReadOnly, Category = "Mana", ReplicatedUsing=OnRep_Mana)
	FGameplayAttributeData Mana;
	ATTRIBUTE_ACCESSORS(URPGAttributeSet, Mana)

	/** MaxMana is its own attribute, since GameplayEffects may modify it */
	UPROPERTY(BlueprintReadOnly, Category = "Mana", ReplicatedUsing=OnRep_MaxMana)
	FGameplayAttributeData MaxMana;
	ATTRIBUTE_ACCESSORS(URPGAttributeSet, MaxMana)

	/** AttackPower of the attacker is multiplied by the base Damage to reduce health, so 1.0 means no bonus */
	UPROPERTY(BlueprintReadOnly, Category = "Damage", ReplicatedUsing = OnRep_AttackPower)
	FGameplayAttributeData AttackPower;
	ATTRIBUTE_ACCESSORS(URPGAttributeSet, AttackPower)

	/** Base Damage is divided by DefensePower to get actual damage done, so 1.0 means no bonus */
	UPROPERTY(BlueprintReadOnly, Category = "Damage", ReplicatedUsing = OnRep_DefensePower)
	FGameplayAttributeData DefensePower;
	ATTRIBUTE_ACCESSORS(URPGAttributeSet, DefensePower)

	/** MoveSpeed affects how fast characters can move */
	UPROPERTY(BlueprintReadOnly, Category = "MoveSpeed", ReplicatedUsing = OnRep_MoveSpeed)
	FGameplayAttributeData MoveSpeed;
	ATTRIBUTE_ACCESSORS(URPGAttributeSet, MoveSpeed)

	/** Damage is a 'temporary' attribute used by the DamageExecution to calculate final damage, which then turns into -Health */
	UPROPERTY(BlueprintReadOnly, Category = "Damage")
	FGameplayAttributeData Damage;
	ATTRIBUTE_ACCESSORS(URPGAttributeSet, Damage)

protected:
	/** Helper function to proportionally adjust the value of an attribute when it's associated max attribute changes. (i.e. When MaxHealth increases, Health increases by an amount that maintains the same percentage as before) */
	void AdjustAttributeForMaxChange(FGameplayAttributeData& AffectedAttribute, const FGameplayAttributeData& MaxAttribute, float NewMaxValue, const FGameplayAttribute& AffectedAttributeProperty);

	// These OnRep functions exist to make sure that the ability system internal representations are synchronized properly during replication
	UFUNCTION()
	virtual void OnRep_Health(const FGameplayAttributeData& OldValue);

	UFUNCTION()
	virtual void OnRep_MaxHealth(const FGameplayAttributeData& OldValue);

	UFUNCTION()
	virtual void OnRep_Mana(const FGameplayAttributeData& OldValue);

	UFUNCTION()
	virtual void OnRep_MaxMana(const FGameplayAttributeData& OldValue);

	UFUNCTION()
	virtual void OnRep_AttackPower(const FGameplayAttributeData& OldValue);

	UFUNCTION()
	virtual void OnRep_DefensePower(const FGameplayAttributeData& OldValue);

	UFUNCTION()
	virtual void OnRep_MoveSpeed(const FGameplayAttributeData& OldValue);
};

// Copyright Epic Games, Inc. All Rights Reserved.

#include "Abilities/RPGAttributeSet.h"
#include "Abilities/RPGAbilitySystemComponent.h"
#include "RPGCharacterBase.h"
#include "GameplayEffect.h"
#include "GameplayEffectExtension.h"

URPGAttributeSet::URPGAttributeSet()
	: Health(1.f)
	, MaxHealth(1.f)
	, Mana(0.f)
	, MaxMana(0.f)
	, AttackPower(1.0f)
	, DefensePower(1.0f)
	, MoveSpeed(1.0f)
	, Damage(0.0f)
{
}

void URPGAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	DOREPLIFETIME(URPGAttributeSet, Health);
	DOREPLIFETIME(URPGAttributeSet, MaxHealth);
	DOREPLIFETIME(URPGAttributeSet, Mana);
	DOREPLIFETIME(URPGAttributeSet, MaxMana);
	DOREPLIFETIME(URPGAttributeSet, AttackPower);
	DOREPLIFETIME(URPGAttributeSet, DefensePower);
	DOREPLIFETIME(URPGAttributeSet, MoveSpeed);
}

void URPGAttributeSet::OnRep_Health(const FGameplayAttributeData& OldValue)
{
	GAMEPLAYATTRIBUTE_REPNOTIFY(URPGAttributeSet, Health, OldValue);
}

void URPGAttributeSet::OnRep_MaxHealth(const FGameplayAttributeData& OldValue)
{
	GAMEPLAYATTRIBUTE_REPNOTIFY(URPGAttributeSet, MaxHealth, OldValue);
}

void URPGAttributeSet::OnRep_Mana(const FGameplayAttributeData& OldValue)
{
	GAMEPLAYATTRIBUTE_REPNOTIFY(URPGAttributeSet, Mana, OldValue);
}

void URPGAttributeSet::OnRep_MaxMana(const FGameplayAttributeData& OldValue)
{
	GAMEPLAYATTRIBUTE_REPNOTIFY(URPGAttributeSet, MaxMana, OldValue);
}

void URPGAttributeSet::OnRep_AttackPower(const FGameplayAttributeData& OldValue)
{
	GAMEPLAYATTRIBUTE_REPNOTIFY(URPGAttributeSet, AttackPower, OldValue);
}

void URPGAttributeSet::OnRep_DefensePower(const FGameplayAttributeData& OldValue)
{
	GAMEPLAYATTRIBUTE_REPNOTIFY(URPGAttributeSet, DefensePower, OldValue);
}

void URPGAttributeSet::OnRep_MoveSpeed(const FGameplayAttributeData& OldValue)
{
	GAMEPLAYATTRIBUTE_REPNOTIFY(URPGAttributeSet, MoveSpeed, OldValue);
}

void URPGAttributeSet::AdjustAttributeForMaxChange(FGameplayAttributeData& AffectedAttribute, const FGameplayAttributeData& MaxAttribute, float NewMaxValue, const FGameplayAttribute& AffectedAttributeProperty)
{
	UAbilitySystemComponent* AbilityComp = GetOwningAbilitySystemComponent();
	const float CurrentMaxValue = MaxAttribute.GetCurrentValue();
	if (!FMath::IsNearlyEqual(CurrentMaxValue, NewMaxValue) && AbilityComp)
	{
		// Change current value to maintain the current Val / Max percent
		const float CurrentValue = AffectedAttribute.GetCurrentValue();
		float NewDelta = (CurrentMaxValue > 0.f) ? (CurrentValue * NewMaxValue / CurrentMaxValue) - CurrentValue : NewMaxValue;

		AbilityComp->ApplyModToAttributeUnsafe(AffectedAttributeProperty, EGameplayModOp::Additive, NewDelta);
	}
}

void URPGAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
	// This is called whenever attributes change, so for max health/mana we want to scale the current totals to match
	Super::PreAttributeChange(Attribute, NewValue);

	if (Attribute == GetMaxHealthAttribute())
	{
		AdjustAttributeForMaxChange(Health, MaxHealth, NewValue, GetHealthAttribute());
	}
	else if (Attribute == GetMaxManaAttribute())
	{
		AdjustAttributeForMaxChange(Mana, MaxMana, NewValue, GetManaAttribute());
	}
}

void URPGAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
	Super::PostGameplayEffectExecute(Data);

	FGameplayEffectContextHandle Context = Data.EffectSpec.GetContext();
	UAbilitySystemComponent* Source = Context.GetOriginalInstigatorAbilitySystemComponent();
	const FGameplayTagContainer& SourceTags = *Data.EffectSpec.CapturedSourceTags.GetAggregatedTags();

	// Compute the delta between old and new, if it is available
	float DeltaValue = 0;
	if (Data.EvaluatedData.ModifierOp == EGameplayModOp::Type::Additive)
	{
		// If this was additive, store the raw delta value to be passed along later
		DeltaValue = Data.EvaluatedData.Magnitude;
	}

	// Get the Target actor, which should be our owner
	AActor* TargetActor = nullptr;
	AController* TargetController = nullptr;
	ARPGCharacterBase* TargetCharacter = nullptr;
	if (Data.Target.AbilityActorInfo.IsValid() && Data.Target.AbilityActorInfo->AvatarActor.IsValid())
	{
		TargetActor = Data.Target.AbilityActorInfo->AvatarActor.Get();
		TargetController = Data.Target.AbilityActorInfo->PlayerController.Get();
		TargetCharacter = Cast<ARPGCharacterBase>(TargetActor);
	}

	if (Data.EvaluatedData.Attribute == GetDamageAttribute())
	{
		// Get the Source actor
		AActor* SourceActor = nullptr;
		AController* SourceController = nullptr;
		ARPGCharacterBase* SourceCharacter = nullptr;
		if (Source && Source->AbilityActorInfo.IsValid() && Source->AbilityActorInfo->AvatarActor.IsValid())
		{
			SourceActor = Source->AbilityActorInfo->AvatarActor.Get();
			SourceController = Source->AbilityActorInfo->PlayerController.Get();
			if (SourceController == nullptr && SourceActor != nullptr)
			{
				if (APawn* Pawn = Cast<APawn>(SourceActor))
				{
					SourceController = Pawn->GetController();
				}
			}

			// Use the controller to find the source pawn
			if (SourceController)
			{
				SourceCharacter = Cast<ARPGCharacterBase>(SourceController->GetPawn());
			}
			else
			{
				SourceCharacter = Cast<ARPGCharacterBase>(SourceActor);
			}

			// Set the causer actor based on context if it's set
			if (Context.GetEffectCauser())
			{
				SourceActor = Context.GetEffectCauser();
			}
		}

		// Try to extract a hit result
		FHitResult HitResult;
		if (Context.GetHitResult())
		{
			HitResult = *Context.GetHitResult();
		}

		// Store a local copy of the amount of damage done and clear the damage attribute
		const float LocalDamageDone = GetDamage();
		SetDamage(0.f);

		if (LocalDamageDone > 0)
		{
			// Apply the health change and then clamp it
			const float OldHealth = GetHealth();
			SetHealth(FMath::Clamp(OldHealth - LocalDamageDone, 0.0f, GetMaxHealth()));

			if (TargetCharacter)
			{
				// This is proper damage
				TargetCharacter->HandleDamage(LocalDamageDone, HitResult, SourceTags, SourceCharacter, SourceActor);

				// Call for all health changes
				TargetCharacter->HandleHealthChanged(-LocalDamageDone, SourceTags);
			}
		}
	}
	else if (Data.EvaluatedData.Attribute == GetHealthAttribute())
	{
		// Handle other health changes such as from healing or direct modifiers
		// First clamp it
		SetHealth(FMath::Clamp(GetHealth(), 0.0f, GetMaxHealth()));

		if (TargetCharacter)
		{
			// Call for all health changes
			TargetCharacter->HandleHealthChanged(DeltaValue, SourceTags);
		}
	}
	else if (Data.EvaluatedData.Attribute == GetManaAttribute())
	{
		// Clamp mana
		SetMana(FMath::Clamp(GetMana(), 0.0f, GetMaxMana()));

		if (TargetCharacter)
		{
			// Call for all mana changes
			TargetCharacter->HandleManaChanged(DeltaValue, SourceTags);
		}
	}
	else if (Data.EvaluatedData.Attribute == GetMoveSpeedAttribute())
	{
		if (TargetCharacter)
		{
			// Call for all movespeed changes
			TargetCharacter->HandleMoveSpeedChanged(DeltaValue, SourceTags);
		}
	}
}

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在Android中,可以通过以下代码来创建 AttributeSet: ``` XmlPullParser parser = context.getResources().getXml(R.xml.my_attributes); AttributeSet attributes = Xml.asAttributeSet(parser); ``` 其中,R.xml.my_attributes是你定义的XML文件的资源ID,可以在XML文件中定义一组自定义属性,例如: ``` <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="MyView"> <attr name="my_custom_attribute" format="string" /> </declare-styleable> </resources> ``` 这个XML文件定义了一个名为MyView的自定义View,在该View中可以使用一个名为my_custom_attribute的自定义属性。在创建 AttributeSet 的时候,可以将该XML文件作为参数传入,这样就可以创建一个包含自定义属性的 AttributeSet 对象了。 ### 回答2: 创建 AttributeSet 可以通过以下步骤进行。 首先,我们需要在代码中创建一个新的 AttributeSet 对象。可以通过使用 Context 的 obtainStyledAttributes() 方法来实现。此方法返回一个 TypedArray 对象,其中包含了 AttributeSet 的值。 接下来,我们需要从我们已经创建的 AttributeSet 对象中获取我们想要的属性值。可以使用 TypedArray 的各种方法来获取属性值,比如 getBoolean()、getDimension()、getString() 等。我们需要根据我们需要的属性类型来选择正确的方法。 在获取完所有属性值后,我们可以使用 TypedArray 的 recycle() 方法来释放已经不再需要的资源。这个方法非常重要,因为它可以帮助我们防止内存泄漏。 最后,我们可以根据获取到的属性值调整我们的视图或其他逻辑。比如,如果我们获取到一个布尔值属性,我们可以根据其值来显示或隐藏某个控件;如果我们获取到一个字符串属性,我们可以将其设置为 TextView 的文本内容。 总的来说,创建 AttributeSet 的过程需要使用 Context 的 obtainStyledAttributes() 方法来获取 TypedArray 对象,然后使用 TypedArray 的各种方法来获取属性值,最后使用 recycle() 方法来释放资源。这样我们就能够得到一个有效的 AttributeSet 对象,并根据其属性值来做进一步的处理。 ### 回答3: 在Android开发中,我们可以使用 AttributeSet 来创建自定义 View 的属性集合。下面我将简要说明如何创建 AttributeSet。 1. 创建 attrs.xml 文件:首先,在 res/values 文件夹下创建一个新的 xml 文件,命名为 attrs.xml。这个文件将用于定义自定义属性的名称和类型。 2. 定义自定义属性:在 attrs.xml 文件中,我们可以使用 <declare-styleable> 标签来定义一个新的属性集合。在该标签内,可以使用 <attr> 标签来定义具体的属性。例如: ``` <resources> <declare-styleable name="CustomView"> <attr name="customAttribute1" format="string" /> <attr name="customAttribute2" format="integer" /> </declare-styleable> </resources> ``` 在上面的代码中,我们定义了一个名为 CustomView 的属性集合,并在该属性集合中添加了两个自定义属性,分别是 customAttribute1 和 customAttribute2,其类型分别为字符串和整数。 3. 使用 AttributeSet:在自定义 View 的构造方法中,我们可以使用 AttributeSet 参数来获取传入 View 的属性集合。例如: ``` public CustomView(Context context, AttributeSet attrs) { super(context, attrs); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomView); String customAttribute1 = typedArray.getString(R.styleable.CustomView_customAttribute1); int customAttribute2 = typedArray.getInt(R.styleable.CustomView_customAttribute2, defaultValue); // 使用获取到的属性值进行相关操作 typedArray.recycle(); } ``` 在上面的代码中,我们通过 context.obtainStyledAttributes 方法获取了属性集合的 TypedArray 对象。然后,通过指定自定义属性的名称来获取对应的属性值。最后,记得使用 typedArray.recycle() 方法来回收 TypedArray 对象。 总结:通过创建 attrs.xml 文件并定义自定义属性,然后在自定义 View 的构造方法中使用 AttributeSet 参数来获取属性集合的 TypedArray 对象,我们就可以轻松地创建和使用 AttributeSet
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值