You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android自定义ContentPageRenderer触发TargetInvocationException求助

问题分析与解决方案

一、先解决TargetInvocationException异常问题

你提到注释掉OnElementChanged里的代码还是会触发异常,那大概率不是你写的逻辑导致的,而是渲染器的基类初始化或者注册环节出了问题。我之前处理过类似的Xamarin.Forms渲染器异常,给你几个具体的排查方向:

  • 检查渲染器注册的类型引用是否正确:
    你的ExportRenderer特性里,typeof(CropDetailPage)指向共享项目的页面,要确认这个类型的命名空间有没有被完整引用。比如如果共享项目的命名空间是MyApp.Shared,那特性里应该写typeof(MyApp.Shared.CropDetailPage),如果漏了命名空间,会导致反射找不到目标类型,直接抛出这个异常。

  • 确认PageRenderer的继承是否正确:
    如果你用的是较新版本的Xamarin.Forms,要确保继承的是Xamarin.Forms.Platform.Android.PageRenderer,而不是不小心引用了Android原生的同名类(虽然概率低,但也有可能踩坑)。错误的基类继承会直接导致初始化失败。

  • 检查依赖版本兼容性:
    一定要保证共享项目和Android项目的Xamarin.Forms版本完全一致,版本不匹配是这类反射异常的高发原因。另外,你代码里用的Android.Support.V7.Widget.Toolbar已经被弃用了,现在推荐改用AndroidX.AppCompat.Widget.Toolbar,旧的Support库和新版本Forms兼容度很差,这也可能是触发异常的元凶。

二、关于移动Toolbar按钮的方式是否正确?

直接通过bar.GetChildAt(0).SetX(10)修改子View坐标的方式非常不可靠,主要有两个问题:

  1. Toolbar的子View结构不是固定的——不同Android版本、不同Forms版本下,Toolbar的子View顺序可能完全不一样,你以为的第0个是按钮,实际可能是标题容器或者其他控件;
  2. 硬编码的坐标在屏幕旋转、不同分辨率设备上会直接出现布局错乱,适配性极差。

正确的实现方式(两种可选)

方法1:自定义Toolbar布局(灵活度高)

如果需要定制复杂的Toolbar布局,推荐直接替换默认Toolbar:

  1. 在Android项目的Resources/layout文件夹下创建CustomToolbar.axml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    android:background="@color/azure">

    <!-- 左侧放置返回按钮 -->
    <Button
        android:id="@+id/back_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Back"
        android:textColor="@android:color/white"/>

    <!-- 中间放置标题 -->
    <TextView
        android:id="@+id/toolbar_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:textColor="@android:color/white"
        android:textSize="18sp"/>

</androidx.appcompat.widget.Toolbar>
  1. 在渲染器中替换默认Toolbar并绑定Command:
[assembly: ExportRenderer(typeof(CropDetailPage), typeof(CropDetailPageRenderer))]
namespace MyApp.Droid
{
    public class CropDetailPageRenderer : PageRenderer
    {
        public CropDetailPageRenderer(Context context) : base(context) { }

        protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
        {
            base.OnElementChanged(e);

            if (e.OldElement != null || Element == null)
                return;

            // 加载自定义Toolbar布局
            var toolbarLayout = LayoutInflater.From(Context).Inflate(Resource.Layout.CustomToolbar, this, false);
            var toolbar = toolbarLayout.FindViewById<AndroidX.AppCompat.Widget.Toolbar>(Resource.Id.toolbar);
            var backBtn = toolbarLayout.FindViewById<Button>(Resource.Id.back_btn);
            var titleTv = toolbarLayout.FindViewById<TextView>(Resource.Id.toolbar_title);

            // 设置页面标题
            titleTv.Text = Element.Title;

            // 绑定返回按钮的Command
            if (Element is CropDetailPage page && page.BindingContext is CropDetailViewModel vm)
            {
                backBtn.Click += (s, args) => vm.GoBackCommand.Execute(null);
            }

            // 将自定义Toolbar添加到页面
            AddView(toolbarLayout);
        }

        protected override void OnLayout(bool changed, int l, int t, int r, int b)
        {
            base.OnLayout(changed, l, t, r, b);
            // 确保自定义Toolbar占满顶部区域
            var toolbar = GetChildAt(0);
            toolbar.Layout(0, 0, r - l, (int)Context.Resources.GetDimension(Resource.Dimension.abc_action_bar_default_height_material));
        }
    }
}

方法2:利用Toolbar的NavigationIcon(简单快捷)

如果只是需要一个左侧返回按钮,直接用Toolbar自带的NavigationIcon更简单:

protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
    base.OnElementChanged(e);

    if (e.OldElement != null || Element == null)
        return;

    var toolbar = FindViewById<AndroidX.AppCompat.Widget.Toolbar>(Resource.Id.toolbar);
    if (toolbar != null)
    {
        // 设置左侧返回图标(替换成你的自定义图标)
        toolbar.NavigationIcon = Context.GetDrawable(Resource.Drawable.ic_back_white);
        // 绑定点击事件到ViewModel的Command
        toolbar.SetNavigationOnClickListener(v =>
        {
            if (Element is CropDetailPage page)
            {
                (page.BindingContext as CropDetailViewModel)?.GoBackCommand.Execute(null);
            }
        });

        // 移除原来的ToolbarItem(避免重复显示)
        Element.ToolbarItems.Clear();
    }
}

总结解决思路

  1. 先解决异常:从渲染器注册的类型引用、基类继承、依赖版本兼容性这三个核心方向排查,确保渲染器能被正确初始化;
  2. 替换按钮移动方案:放弃硬改子View坐标的方式,改用自定义Toolbar布局或者NavigationIcon的方案,既能保证功能实现,又能提升兼容性和稳定性。

内容的提问来源于stack exchange,提问作者Greggz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:56:07