【问题标题】:Android onClick method doesn't work on a custom viewAndroid onClick 方法不适用于自定义视图
【发布时间】:2023-03-03 06:10:22
【问题描述】:

我已经开始开发一个应用程序。我昨天构建了菜单,但是 onClick 方法不起作用! 我创建了一个扩展 View 的类并将其命名为 MainMenuObject - 该类适用于主菜单中的任何对象(按钮、徽标等)。我为他们创建了一个特殊的类,因为当菜单启动时我正在做一个动画。在我构建了 MainMenuObject 类之后,我构建了另一个类 (OpeningTimesView),它扩展了 View,其中包含主菜单的所有按钮,并将用作主 Activity 的布局。

一切都很好,动画效果很好,我想在我的按钮上放置监听器,所以我在 OpeningTimesView 类中添加了 onClickListener 的实现,并重写了 onClick 方法。然后我使用 setOnClickListener(this) 和 setClickable(true) 将侦听器添加到按钮中,但它不起作用!我什么都试过了!请帮我弄清楚我做错了什么。我在 onClick 方法中添加了一个 toast,它不依赖于任何“if”,但它也不会显示。

(顺便说一句,有没有办法将屏幕宽度和高度定义为所有类都可以访问的变量?它不能是静态的,因为您从显示对象获取高度和宽度,但必须有另一种方式)

这是代码:

public class OpeningTimesView extends View implements OnClickListener{
    private MainMenuObjectView searchButton;
    private MainMenuObjectView supportButton;
    private MainMenuObjectView aboutButton;
    private int screenWidth;
    private int screenHeight;
    public OpeningTimesView(Context context, Display dis) {
        super(context);

        this.screenWidth = dis.getWidth();
        this.screenHeight = dis.getHeight();

        searchButton = new MainMenuObjectView(context, 200, MovingMode.RIGHT, R.drawable.search, dis);
        supportButton = new MainMenuObjectView(context, 400, MovingMode.LEFT, R.drawable.support, dis);
        aboutButton = new MainMenuObjectView(context, 600, MovingMode.RIGHT, R.drawable.about, dis);

        searchButton.setClickable(true);
        supportButton.setClickable(true);
        aboutButton.setClickable(true);

        searchButton.setOnClickListener(this);
        supportButton.setOnClickListener(this);
        aboutButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View view){
        Toast.makeText(getContext(), "Search button pressed", Toast.LENGTH_SHORT).show();
        if(view == searchButton){
            Toast.makeText(getContext(), "Search button pressed", Toast.LENGTH_SHORT).show();
        }
        else if(view == supportButton){
            Toast.makeText(getContext(), "Support button pressed", Toast.LENGTH_SHORT).show();
        }
        else Toast.makeText(getContext(), "About button pressed", Toast.LENGTH_SHORT).show();
    }
    @Override
    public void onDraw(Canvas canvas)
    {
        // Drawing the buttons
        this.searchButton.onDraw(canvas);
        this.aboutButton.onDraw(canvas);
        this.supportButton.onDraw(canvas);
    }

提前致谢,埃拉德!

【问题讨论】:

标签: android android-view android-custom-view


【解决方案1】:

我刚刚遇到了同样的问题 - 我创建了一个自定义视图,当我通过调用 v.setOnClickListener(new OnClickListener() {...}); 在活动中为它注册一个新的监听器时,监听器没有被调用。

在我的自定义视图中,我还覆盖了public boolean onTouchEvent(MotionEvent event) {...} 方法。问题是我没有调用 View 类的方法 - super.onTouchEvent(event)。这解决了问题。所以如果你想知道为什么你的监听器没有被调用,你可能忘记调用超类的 onTouchEvent 方法

这是一个简单的例子:

private static class CustomView extends View implements View.OnClickListener {
    public CustomView(Context context) {
        super(context);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);   // this super call is important !!!
        // YOUR LOGIC HERE
        return true;
    }

    @Override
    public void onClick(View v) {
        // DO SOMETHING HERE
    }
}

【讨论】:

  • 嗯,请添加代码以便我们理解。你在哪里调用方法...
  • 感谢您的评论-我添加了一个简单的示例,希望现在更清楚
【解决方案2】:

如果您不熟悉 UI 框架的操作方式,在 Android 中创建自定义控件可能会很棘手。如果您还没有,我建议您阅读以下内容:

http://developer.android.com/guide/topics/ui/declaring-layout.html

http://developer.android.com/guide/topics/ui/custom-components.html

http://developer.android.com/guide/topics/ui/layout-objects.html

请注意,在 XML 中声明布局时,元素是嵌套的。这将创建一个布局层次结构,您必须在仅使用 Java 代码自定义组件时自行创建该层次结构。

您很可能陷入了 Android 的触控层次结构。与其他一些流行的移动平台不同,Android 从 View 层次结构的顶部开始提供触摸事件,然后向下工作。传统上占据较高层次结构(Activity 和 Layouts)的类在它们中具有转发触摸的逻辑,它们本身并不消耗。

因此,我建议您更改您的 OpeningTimesView 以扩展 ViewGroup(所有 Android 布局的超类)或特定布局(LinearLayoutRelativeLayout 等)并添加您的按钮作为孩子。目前,似乎没有定义的层次结构(按钮实际上并没有“包含”在容器中,它们只是成员),这可能会混淆事件真正去向的问题。

  1. 触摸应该更自然地流向按钮,从而触发您的点击事件
  2. 您可以利用 Android 的布局机制来绘制视图,而不是依赖绘制代码来完成所有这些工作。

选择一个布局类来帮助您将按钮放置在它们的FINAL位置。您可以使用 Android 中的动画框架或自定义绘图代码(就像您现在拥有的那样)以任何您喜欢的方式为它们设置动画。如果需要,按钮的位置和该按钮当前绘制的位置允许有很大的不同,这就是当前动画框架在 Android(3.0 之前)中的工作方式......但这是一个单独的问题。您还拥有AbsoluteLayout,它允许您在任何您喜欢的位置放置和替换对象...但请注意您的应用在所有使用此设备的 Android 设备上的外观(考虑到不同的屏幕尺寸)。

关于显示信息的第二点。 最简单的方法可能就是在需要的地方使用Context.getResources().getDisplayMetrics()Activity继承自Context,所以他们可以直接调用这个方法。视图总是有一个Context,您可以使用getContext() 访问。任何其他类,您都可以在构造中将Context 作为参数传递(这是Android 中的常见模式,您会看到许多对象需要Context,主要是为了访问Resources)。

这是一个快速启动的框架示例。这只是将三个水平排列一次作为最终位置:

Public class OpeningTimesView extends LinearLayout implements OnClickListener {
    private MainMenuObjectView searchButton;
    private MainMenuObjectView supportButton;
    private MainMenuObjectView aboutButton;
    private int screenWidth;
    private int screenHeight;

    public OpeningTimesView(Context context) {
        this(context, null);
    }

    //Thus constructor gets used if you ever instantiate your component from XML
    public OpeningTimesView(Context context, AttributeSet attrs) {
        super(context, attrs);

        /* This is a better way to obtain your screen info
        DisplayMetrics display = context.getResources().getDisplayMetrics();
        screenWidth = display.widthPixels;
        screenHeight = display.heightPixels;
        */
        //This way works also, without needing to customize the constructor
        WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
        Display dis = wm.getDefaultDisplay();
        screenWidth = dis.getWidth();
        screenHeight = dis.getHeight();

        searchButton = new MainMenuObjectView(context, 200, MovingMode.RIGHT, R.drawable.search, dis);
        supportButton = new MainMenuObjectView(context, 400, MovingMode.LEFT, R.drawable.support, dis);
        aboutButton = new MainMenuObjectView(context, 600, MovingMode.RIGHT, R.drawable.about, dis);

        //Even if they don't extend button, if MainMenuObjectView is always clickable
        // this should probably be brought into that class's constructor
        searchButton.setClickable(true);
        supportButton.setClickable(true);
        aboutButton.setClickable(true);

        searchButton.setOnClickListener(this);
        supportButton.setOnClickListener(this);
        aboutButton.setOnClickListener(this);

        //Add the buttons to the layout (the buttons are now children of the container)
        setOrientation(LinearLayout.HORIZONTAL);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        addView(searchButton, params);
        addView(supportButton, params);
        addView(aboutButton, params);       
    }

    @Override
    public void onClick(View view){
        Toast.makeText(getContext(), "Search button pressed", Toast.LENGTH_SHORT).show();
        if(view == searchButton){
            Toast.makeText(getContext(), "Search button pressed", Toast.LENGTH_SHORT).show();
        }
        else if(view == supportButton){
            Toast.makeText(getContext(), "Support button pressed", Toast.LENGTH_SHORT).show();
        }
        else Toast.makeText(getContext(), "About button pressed", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onDraw(Canvas canvas)
    {
        //Drawing the buttons
        // This may only be necessary until they are in place, then just call super.onDraw(canvas)
        this.searchButton.onDraw(canvas);
        this.aboutButton.onDraw(canvas);
        this.supportButton.onDraw(canvas);
    }    
}

您可以从那里自定义它。可能会启动可见性设置为 View.INVISIBLE 的按钮,直到您使用绘图代码或自定义动画对象为它们设置动画,然后使它们在最终的静止位置可见。

不过,这里的关键是布局足够聪明,知道当它接收到一个触摸事件时,它应该将它转发给相应的子级。您可以在没有此功能的情况下创建自定义视图,但您必须拦截容器上的所有触摸并进行数学运算以确定手动将事件转发到哪个子视图。如果你真的不能让任何布局管理器工作,这是你的办法。

希望有帮助!

【讨论】:

  • 首先感谢您的回答!我试图扩展 ViewGroup,但我需要实现 onLayout() 方法,我真的不知道它是什么,我试图将其留空,但是当我运行应用程序时出现黑屏。我不能真正使用任何特定布局,因为我正在制作动画并且按钮和标题不是静态的。我不明白如何让按钮成为孩子而不仅仅是成员。如果您能详细解释一下自己,我会很高兴,因为我真的是 Android 新手 :) 对于屏幕尺寸,谢谢,它对我帮助很大!
  • 尝试添加更多信息,希望有助于阐明 Android 上下文(双关语)中的构建组件。一旦您熟悉了 Android 管理 UI 的方式,自定义 Android 组件就不会那么麻烦了。干杯。
  • 谢谢伙计。这就像一篇小文章。对我很有用。我很惊讶为什么它没有得到这么多的支持。
【解决方案3】:

您可以在自定义视图的 onTouchEvent 中调用 performClick()。

在您的自定义视图中使用它:

    @Override
    public boolean onTouchEvent(final MotionEvent event) {
        if(event.getAction() == MotionEvent.ACTION_UP){
            return performClick();
        }
        return true;
    }

【讨论】:

    【解决方案4】:

    我这样做:

    public class YourView extends LinearLayout implements OnClickListener {
        OnClickListener listener;
    
        //... constructors
    
        public void setOnClickListener(OnClickListener listener) {
            this.listener = listener;
        }
    
        @Override
        public void onClick(View v) {
            if (listener != null)
                 listener.onClick(v);
        }
    }
    

    【讨论】:

    • 这似乎运作良好。当我实现我的自定义视图组时,我让 onClick() 在我的视图组内部触发,但在外部,在活动中它不会触发。所以我使用了这种方法。作品。 注意:我无法让黄油刀使用它。
    【解决方案5】:

    您必须在构造函数中调用setOnClickListener(this) 并在自身上实现View.OnClickListener

    这样:

    public class MyView extends View implements View.OnClickListener {
    
        public MyView(Context context) {
            super(context);
            setOnClickListener(this);
        }
    
        public MyView(Context context, AttributeSet attrs) {
            super(context, attrs);
            setOnClickListener(this);
        }
    
        @Override
        public void onClick(View v) {
            Toast.makeText(getContext(), "On click.", Toast.LENGTH_SHORT).show();
        }
    }
    

    【讨论】:

      【解决方案6】:

      我遇到了同样的问题。在我的例子中,我有一个 LinearLayout 作为我的自定义视图的根元素,clickablefocusable 设置为 true,自定义视图标签本身(在片段布局中使用)也设置为 clickablefocusable。事实证明,要让它工作,我唯一要做的就是从 XML 中删除所有 clickablefocusable 属性:) 违反直觉,但它起作用了。

      【讨论】:

        【解决方案7】:

        MainMenuObjectView 类中实现onClickListener,因为这些对象会响应点击。
        另一种选择是扩展Button 而不是View,因为您只使用其中的按钮


        更新:完整示例


        这是将其直接实现到可点击视图中的想法。有一个 TestView 类扩展 View 并根据您的需要覆盖 onDraw,并且还响应点击。我省略了任何动画实现,因为您拥有该部分,它与 ClickListener 讨论无关。
        我在 Eclair 模拟器中对其进行了测试,它按预期工作(单击后显示 Toast 消息)。

        文件:Test.java

        package com.aleadam.test;
        
        import android.app.Activity;
        import android.os.Bundle;
        import android.widget.LinearLayout;
        import android.widget.TextView;
        
        public class Test extends Activity {
            /** Called when the activity is first created. */
            @Override
            public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                LinearLayout ll = new LinearLayout(this);
                ll.setOrientation(LinearLayout.VERTICAL);
                TextView label = new TextView(this);
                LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
                     LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
                label.setText("Click the circle!");
                TestView testView = new TestView(this);
                ll.addView(label, layoutParams);
                ll.addView(testView, layoutParams);
                setContentView(ll);
            }
        }
        

        文件:TestView.java

        package com.aleadam.test;
        
        import android.content.Context;
        import android.graphics.Canvas;
        import android.graphics.Color;
        import android.graphics.Paint;
        import android.view.View;
        import android.view.View.OnClickListener;
        import android.widget.Toast;
        
        public class TestView extends View implements OnClickListener {
            Context context;
        
            public TestView(Context context) {
                super(context);
                this.context = context;
                setOnClickListener(this);
            }
        
            public void onClick(View arg0) {
                Toast.makeText(context, "View clicked.", Toast.LENGTH_SHORT).show();
            }
        
            @Override
            public void onDraw (Canvas canvas) {
                super.onDraw(canvas);
                this.setBackgroundColor(Color.LTGRAY);
                Paint paint = new Paint (Paint.ANTI_ALIAS_FLAG);
                paint.setColor(Color.RED);
                canvas.drawCircle(20, 20, 20, paint);
            }
        }
        

        如果你需要一些可点击的和一些不可点击的,你可以添加一个带有 用于确定 ClickListener 是否附加到视图的布尔参数:

        public TestView(Context context, boolean clickable) {
            super(context);
            this.context = context;
            if (clickable)
                setOnClickListener(this);
        }
        

        【讨论】:

        • 这并不会真正改变这里的行为,监听器设计模式的全部意义在于生成事件的对象不需要是处理它的对象。
        • @Wireless 相反,它会简化问题。我并不是说它是唯一可用的选项,但是对于我所看到的这个示例,它会消除您提到的关于视图层次结构和事件处理的问题。
        • @Aleadam 该代码中的单击事件永远不会触发,因为创建它的总体触摸事件不会发送到正确的视图。这不会通过移动点击监听器来解决。
        • 我认为,如果您在同一个视图中实现侦听器,则这是不正确的(就像在 MainMenuObjectView 类中执行它或扩展 Button 而不是 View 一样。但是,我这里没有 Eclipse 和模拟器,所以我将在今晚晚些时候进行正式测试。
        • MainMenuObjectView 不仅仅用于按钮,我为主菜单中的所有内容创建了它,包括标题,因为我在打开应用程序时正在做动画,所以我需要 onDraw 方法覆盖并添加 move() 方法,因此在单击侦听器上实现此类将是错误的。对于使其扩展 Button 而不是视图的另一种选择,我尝试了它,但它没有帮助,我想这就像 Wireless Designs 说该事件在某个地方被捕获......但无论如何,我非常感谢你的帮助!
        【解决方案8】:

        我有办法! 这并不是针对这个特定问题的真正解决方案,而是一种全新的方法。 我将此线程发送给我认识的人,他告诉我使用 android 具有的动画 SDK(如提到的无线设计),所以我没有用 4 个类来做主菜单页面,我只用一个扩展的类来做Activity 和 Animation 类提供了许多动画选项。 我要感谢你们俩对我的帮助,你们很棒。 如果有人会遇到同样的问题或其他问题,我将添加代码:

        package elad.openapp;
        
        import android.app.Activity;
        import android.content.Intent;
        import android.os.Bundle;
        import android.view.HapticFeedbackConstants;
        import android.view.View;
        import android.view.View.OnClickListener;
        import android.view.Window;
        import android.view.animation.Animation;
        import android.view.animation.AnimationSet;
        import android.view.animation.ScaleAnimation;
        import android.view.animation.TranslateAnimation;
        import android.widget.ImageView;
        import android.widget.Toast;
        
        public class OpeningTimes extends Activity implements OnClickListener{
            /** Called when the activity is first created. */
            @Override
            public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
            // Disabling the title bar..
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            setContentView(R.layout.main);
        
            // Create the buttons and title objects
            ImageView title = (ImageView)findViewById(R.id.title_main);
            ImageView search = (ImageView)findViewById(R.id.search_button_main);
            ImageView support = (ImageView)findViewById(R.id.support_button_main);
            ImageView about = (ImageView)findViewById(R.id.about_button_main);
        
            // Setting the onClick listeners
            search.setOnClickListener(this);
            support.setOnClickListener(this);
            about.setOnClickListener(this);
        
            setButtonsAnimation(title, search, support, about);
        
        }
        
        @Override
        public void onClick(View v) {
            if(v.getId()==R.id.search_button_main){
                v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
                startActivity(new Intent(this,SearchPage.class));
            }
            else if(v.getId()==R.id.support_button_main){
                v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
                Toast.makeText(this, "Coming soon...", Toast.LENGTH_LONG).show();
            }
            else if(v.getId()==R.id.about_button_main){
        
                v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
                Toast.makeText(this, "Coming soon...", Toast.LENGTH_LONG).show();
            }
        }
        
        // Setting the animation on the buttons
        public void setButtonsAnimation(ImageView title, ImageView search, ImageView support, ImageView about){
            // Title animation (two animations - scale and translate)
            AnimationSet animSet = new AnimationSet(true);
        
            Animation anim = new ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f);
            anim.setDuration(750);
            animSet.addAnimation(anim);
        
            anim = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f,
                    Animation.RELATIVE_TO_SELF, -1.0f, Animation.RELATIVE_TO_SELF, 0.0f);
            anim.setDuration(750);
            animSet.addAnimation(anim);
        
            title.startAnimation(animSet);
        
            // Search button animation
            anim = new TranslateAnimation(Animation.RELATIVE_TO_SELF, -1.5f, Animation.RELATIVE_TO_SELF, 0.0f,
                    Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f);
            anim.setDuration(750);
        
            search.startAnimation(anim);
        
            // Support button animation
            anim = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 1.5f, Animation.RELATIVE_TO_SELF, 0.0f,
                    Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f);
            anim.setDuration(750);
        
            support.startAnimation(anim);
        
            // About button animation
            anim = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f,
                    Animation.RELATIVE_TO_SELF, 3f, Animation.RELATIVE_TO_SELF, 0.0f);
            anim.setDuration(750);
        
            about.startAnimation(anim);
        }
        }
        

        【讨论】:

          【解决方案9】:

          在我的情况下,我在自定义视图中有一个 RelativeLayout 作为父级,使其工作的唯一方法是在 RelativeLayout 和自定义视图的构造函数中将 focusableclickable 设置为 true , 膨胀布局后,添加:

          View view = inflate(getContext(), R.layout.layout_my_custom_view, this);
          
          view.findViewById(R.id.theparent).setOnClickListener(new OnClickListener() {
                  @Override
                  public void onClick(View view) {
                      performClick();
                  }
              });
          

          【讨论】:

            【解决方案10】:

            就这么简单:

            public class FancyButton
             extends FrameLayout
             implements View.OnClickListener { ..
            
                void yourSetupFunction(Context context, @Nullable AttributeSet attrs) {
                    ..
                    super.setOnClickListener(this); // NOTE THE SUPER
                }
            
                OnClickListener consumerListener = null;
                @Override
                public void setOnClickListener(@Nullable OnClickListener l) {
                    consumerListener = l;
                    // DO NOT CALL SUPER HERE
                }
            
                @Override
                public void onClick(View v) {
                    Log.i("dev","perform my custom functions, and then ...");
                    if (consumerListener != null) { consumerListener.onClick(v); }
                }
            
            1. 我们实现了 View.OnClickListener,因此有一个
            2. onClick(查看 v)
            3. 在setOnClickListener中,记住从外界设置的监听器
            4. 将实际侦听器设置为我们(使用“super.” ...)
            5. 在onClick你自定义的东西,然后调用外界的onClick

            【讨论】:

              【解决方案11】:

              只需将callOnClick() 添加到您的onTouchEvent() 方法中

              public boolean onTouchEvent(MotionEvent event) {
                      .....YOURCODE.....
                      callOnClick();
                      return boolean;
                  }
              

              【讨论】:

                猜你喜欢
                • 2017-08-13
                • 1970-01-01
                • 1970-01-01
                • 2021-07-07
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多